Page MenuHomeWickedGov Phorge

dm.FiltersViewModel.test.js
No OneTemporary

Size
48 KB
Referenced Files
None
Subscribers
None

dm.FiltersViewModel.test.js

/* eslint-disable camelcase */
( function () {
const rcfilters = require( 'mediawiki.rcfilters.filters.ui' );
const filterDefinition = [ {
name: 'group1',
type: 'send_unselected_if_any',
filters: [
{
name: 'filter1', label: 'group1filter1-label', description: 'group1filter1-desc',
default: true,
cssClass: 'filter1class',
conflicts: [ { group: 'group2' } ],
subset: [
{
group: 'group1',
filter: 'filter2'
},
{
group: 'group1',
filter: 'filter3'
}
]
},
{
name: 'filter2', label: 'group1filter2-label', description: 'group1filter2-desc',
conflicts: [ { group: 'group2', filter: 'filter6' } ],
cssClass: 'filter2class',
subset: [
{
group: 'group1',
filter: 'filter3'
}
]
},
// NOTE: This filter has no highlight!
{ name: 'filter3', label: 'group1filter3-label', description: 'group1filter3-desc', default: true }
]
}, {
name: 'group2',
type: 'send_unselected_if_any',
fullCoverage: true,
conflicts: [ { group: 'group1', filter: 'filter1' } ],
filters: [
{ name: 'filter4', label: 'group2filter4-label', description: 'group2filter4-desc', cssClass: 'filter4class' },
{ name: 'filter5', label: 'group2filter5-label', description: 'group2filter5-desc', default: true, cssClass: 'filter5class' },
{
name: 'filter6', label: 'group2filter6-label', description: 'group2filter6-desc', cssClass: 'filter6class',
conflicts: [ { group: 'group1', filter: 'filter2' } ]
}
]
}, {
name: 'group3',
type: 'string_options',
separator: ',',
default: 'filter8',
filters: [
{ name: 'filter7', label: 'group3filter7-label', description: 'group3filter7-desc', cssClass: 'filter7class' },
{ name: 'filter8', label: 'group3filter8-label', description: 'group3filter8-desc', cssClass: 'filter8class' },
{ name: 'filter9', label: 'group3filter9-label', description: 'group3filter9-desc', cssClass: 'filter9class' }
]
}, {
name: 'group4',
type: 'single_option',
hidden: true,
default: 'option2',
filters: [
// NOTE: The entire group has no highlight supported
{ name: 'option1', label: 'group4option1-label', description: 'group4option1-desc' },
{ name: 'option2', label: 'group4option2-label', description: 'group4option2-desc' },
{ name: 'option3', label: 'group4option3-label', description: 'group4option3-desc' }
]
}, {
name: 'group5',
type: 'single_option',
filters: [
{ name: 'option1', label: 'group5option1-label', description: 'group5option1-desc', cssClass: 'group5opt1class' },
{ name: 'option2', label: 'group5option2-label', description: 'group5option2-desc', cssClass: 'group5opt2class' },
{ name: 'option3', label: 'group5option3-label', description: 'group5option3-desc', cssClass: 'group5opt3class' }
]
}, {
name: 'group6',
type: 'boolean',
sticky: true,
filters: [
{ name: 'group6option1', label: 'group6option1-label', description: 'group6option1-desc', cssClass: 'group6opt1class' },
{ name: 'group6option2', label: 'group6option2-label', description: 'group6option2-desc', default: true, cssClass: 'group6opt2class' },
{ name: 'group6option3', label: 'group6option3-label', description: 'group6option3-desc', default: true, cssClass: 'group6opt3class' }
]
}, {
name: 'group7',
type: 'single_option',
sticky: true,
default: 'group7option2',
filters: [
{ name: 'group7option1', label: 'group7option1-label', description: 'group7option1-desc', cssClass: 'group7opt1class' },
{ name: 'group7option2', label: 'group7option2-label', description: 'group7option2-desc', cssClass: 'group7opt2class' },
{ name: 'group7option3', label: 'group7option3-label', description: 'group7option3-desc', cssClass: 'group7opt3class' }
]
} ],
shortFilterDefinition = [ {
name: 'group1',
type: 'send_unselected_if_any',
filters: [ { name: 'filter1' }, { name: 'filter2' } ]
}, {
name: 'group2',
type: 'boolean',
hidden: true,
filters: [ { name: 'filter3' }, { name: 'filter4' } ]
}, {
name: 'group3',
type: 'string_options',
sticky: true,
default: 'filter6',
filters: [ { name: 'filter5' }, { name: 'filter6' }, { name: 'filter7' } ]
} ],
viewsDefinition = {
namespaces: {
label: 'Namespaces',
trigger: ':',
groups: [ {
name: 'namespace',
label: 'Namespaces',
type: 'string_options',
separator: ';',
filters: [
{ name: 0, label: 'Main', cssClass: 'namespace-0' },
{ name: 1, label: 'Talk', cssClass: 'namespace-1' },
{ name: 2, label: 'User', cssClass: 'namespace-2' },
{ name: 3, label: 'User talk', cssClass: 'namespace-3' }
]
} ]
}
},
defaultParameters = {
filter1: '1',
filter2: '0',
filter3: '1',
filter4: '0',
filter5: '1',
filter6: '0',
group3: 'filter8',
group4: 'option2',
group5: 'option1',
namespace: ''
},
baseParamRepresentation = {
filter1: '0',
filter2: '0',
filter3: '0',
filter4: '0',
filter5: '0',
filter6: '0',
group3: '',
group4: 'option2',
group5: 'option1',
group6option1: '0',
group6option2: '1',
group6option3: '1',
group7: 'group7option2',
namespace: ''
},
emptyParamRepresentation = {
filter1: '0',
filter2: '0',
filter3: '0',
filter4: '0',
filter5: '0',
filter6: '0',
group3: '',
group4: '',
group5: '',
group6option1: '0',
group6option2: '0',
group6option3: '0',
group7: '',
namespace: '',
// Null highlights
group1__filter1_color: null,
group1__filter2_color: null,
// group1__filter3_color: null, // Highlight isn't supported
group2__filter4_color: null,
group2__filter5_color: null,
group2__filter6_color: null,
group3__filter7_color: null,
group3__filter8_color: null,
group3__filter9_color: null,
// group4__option1_color: null, // Highlight isn't supported
// group4__option2_color: null, // Highlight isn't supported
// group4__option3_color: null, // Highlight isn't supported
group5__option1_color: null,
group5__option2_color: null,
group5__option3_color: null,
group6__group6option1_color: null,
group6__group6option2_color: null,
group6__group6option3_color: null,
group7__group7option1_color: null,
group7__group7option2_color: null,
group7__group7option3_color: null,
namespace__0_color: null,
namespace__1_color: null,
namespace__2_color: null,
namespace__3_color: null
},
baseFilterRepresentation = {
group1__filter1: false,
group1__filter2: false,
group1__filter3: false,
group2__filter4: false,
group2__filter5: false,
group2__filter6: false,
group3__filter7: false,
group3__filter8: false,
group3__filter9: false,
// The 'single_value' type of group can't have empty value; it's either
// the default given or the first item that will get the truthy value
group4__option1: false,
group4__option2: true, // Default
group4__option3: false,
group5__option1: true, // No default set, first item is default value
group5__option2: false,
group5__option3: false,
group6__group6option1: false,
group6__group6option2: true,
group6__group6option3: true,
group7__group7option1: false,
group7__group7option2: true,
group7__group7option3: false,
namespace__0: false,
namespace__1: false,
namespace__2: false,
namespace__3: false
},
baseFullFilterState = {
group1__filter1: { selected: false, conflicted: false, included: false },
group1__filter2: { selected: false, conflicted: false, included: false },
group1__filter3: { selected: false, conflicted: false, included: false },
group2__filter4: { selected: false, conflicted: false, included: false },
group2__filter5: { selected: false, conflicted: false, included: false },
group2__filter6: { selected: false, conflicted: false, included: false },
group3__filter7: { selected: false, conflicted: false, included: false },
group3__filter8: { selected: false, conflicted: false, included: false },
group3__filter9: { selected: false, conflicted: false, included: false },
group4__option1: { selected: false, conflicted: false, included: false },
group4__option2: { selected: true, conflicted: false, included: false },
group4__option3: { selected: false, conflicted: false, included: false },
group5__option1: { selected: true, conflicted: false, included: false },
group5__option2: { selected: false, conflicted: false, included: false },
group5__option3: { selected: false, conflicted: false, included: false },
group6__group6option1: { selected: false, conflicted: false, included: false },
group6__group6option2: { selected: true, conflicted: false, included: false },
group6__group6option3: { selected: true, conflicted: false, included: false },
group7__group7option1: { selected: false, conflicted: false, included: false },
group7__group7option2: { selected: true, conflicted: false, included: false },
group7__group7option3: { selected: false, conflicted: false, included: false },
namespace__0: { selected: false, conflicted: false, included: false },
namespace__1: { selected: false, conflicted: false, included: false },
namespace__2: { selected: false, conflicted: false, included: false },
namespace__3: { selected: false, conflicted: false, included: false }
};
QUnit.module( 'mediawiki.rcfilters - FiltersViewModel', QUnit.newMwEnvironment( {
messages: {
'group1filter1-label': 'Group 1: Filter 1 title',
'group1filter1-desc': 'Description of Filter 1 in Group 1',
'group1filter2-label': 'Group 1: Filter 2 title',
'group1filter2-desc': 'Description of Filter 2 in Group 1',
'group1filter3-label': 'Group 1: Filter 3',
'group1filter3-desc': 'Description of Filter 3 in Group 1',
'group2filter4-label': 'Group 2: Filter 4 title',
'group2filter4-desc': 'Description of Filter 4 in Group 2',
'group2filter5-label': 'Group 2: Filter 5',
'group2filter5-desc': 'Description of Filter 5 in Group 2',
'group2filter6-label': 'xGroup 2: Filter 6',
'group2filter6-desc': 'Description of Filter 6 in Group 2'
}
} ) );
QUnit.test( 'Setting up filters', ( assert ) => {
const model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
// Test that all items were created
assert.true(
Object.keys( baseFilterRepresentation ).every( ( filterName ) => model.getItemByName( filterName ) instanceof rcfilters.dm.FilterItem ),
'Filters instantiated and stored correctly'
);
assert.deepEqual(
model.getSelectedState(),
baseFilterRepresentation,
'Initial state of filters'
);
model.toggleFiltersSelected( {
group1__filter1: true,
group2__filter5: true,
group3__filter7: true
} );
assert.deepEqual(
model.getSelectedState(),
$.extend( true, {}, baseFilterRepresentation, {
group1__filter1: true,
group2__filter5: true,
group3__filter7: true
} ),
'Updating filter states correctly'
);
} );
QUnit.test( 'Default filters', ( assert ) => {
const model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
// Empty query = only default values
assert.deepEqual(
model.getDefaultParams(),
defaultParameters,
'Default parameters are stored properly per filter and group (sticky groups are ignored)'
);
} );
QUnit.test( 'Parameter minimal state', ( assert ) => {
const model = new rcfilters.dm.FiltersViewModel(),
cases = [
{
input: {},
result: {},
msg: 'Empty parameter representation produces an empty result'
},
{
input: {
filter1: '1',
filter2: '0',
filter3: '0',
group3: '',
group4: 'option2'
},
result: {
filter1: '1',
group4: 'option2'
},
msg: 'Mixed input results in only non-falsey values as result'
},
{
input: {
filter1: '0',
filter2: '0',
filter3: '0',
group3: '',
group4: '',
group1__filter1_color: null
},
result: {},
msg: 'An all-falsey input results in an empty result.'
},
{
input: {
filter1: '0',
filter2: '0',
filter3: '0',
group3: '',
group4: '',
group1__filter1_color: 'c1'
},
result: {
group1__filter1_color: 'c1'
},
msg: 'An all-falsey input with highlight params result in only the highlight param.'
},
{
input: {
group1__filter1_color: 'c1',
group1__filter3_color: 'c3' // Not supporting highlights
},
result: {
group1__filter1_color: 'c1'
},
msg: 'Unsupported highlights are removed.'
}
];
model.initializeFilters( filterDefinition, viewsDefinition );
cases.forEach( ( test ) => {
assert.deepEqual(
model.getMinimizedParamRepresentation( test.input ),
test.result,
test.msg
);
} );
} );
QUnit.test( 'Parameter states', ( assert ) => {
// Some groups / params have their defaults immediately applied
// to their state. These include single_option which can never
// be empty, etc. These are these states:
const appliedDefaultParameters = {
group4: 'option2',
group5: 'option1',
// Sticky, their defaults apply immediately
group6option2: '1',
group6option3: '1',
group7: 'group7option2'
};
let model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
assert.deepEqual(
model.getEmptyParameterState(),
emptyParamRepresentation,
'Producing an empty parameter state'
);
model.toggleFiltersSelected( {
group1__filter1: true,
group3__filter7: true
} );
assert.deepEqual(
model.getCurrentParameterState(),
// appliedDefaultParams applies the default value to parameters
// who must have an initial value to begin with, so we have to
// take it into account in the current state
$.extend( true, {}, appliedDefaultParameters, {
filter2: '1',
filter3: '1',
group3: 'filter7'
} ),
'Producing a current parameter state'
);
// Reset
model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
const parametersWithoutExcluded = $.extend( true, {}, appliedDefaultParameters );
delete parametersWithoutExcluded.group7;
delete parametersWithoutExcluded.group6option2;
delete parametersWithoutExcluded.group6option3;
assert.deepEqual(
model.getCurrentParameterState( true ),
parametersWithoutExcluded,
'Producing a current clean parameter state without excluded filters'
);
} );
QUnit.test( 'Cleaning up parameter states', ( assert ) => {
const model = new rcfilters.dm.FiltersViewModel(),
cases = [
{
input: {},
result: {},
msg: 'Empty parameter representation produces an empty result'
},
{
input: {
filter1: '1', // Regular (do not strip)
group6option1: '1' // Sticky
},
result: { filter1: '1' },
msg: 'Valid input strips all sticky params regardless of value'
}
];
model.initializeFilters( filterDefinition, viewsDefinition );
cases.forEach( ( test ) => {
assert.deepEqual(
model.removeStickyParams( test.input ),
test.result,
test.msg
);
} );
} );
QUnit.test( 'Finding matching filters', ( assert ) => {
const testCases = [
{
query: 'group',
expectedMatches: {
group1: [ 'group1__filter1', 'group1__filter2', 'group1__filter3' ],
group2: [ 'group2__filter4', 'group2__filter5' ]
},
reason: 'Finds filters starting with the query string'
},
{
query: 'in Group 2',
expectedMatches: {
group2: [ 'group2__filter4', 'group2__filter5', 'group2__filter6' ]
},
reason: 'Finds filters containing the query string in their description'
},
{
query: 'title',
expectedMatches: {
group1: [ 'group1__filter1', 'group1__filter2' ],
group2: [ 'group2__filter4' ]
},
reason: 'Finds filters containing the query string in their group title'
},
{
query: ':Main',
expectedMatches: {
namespace: [ 'namespace__0' ]
},
reason: 'Finds item in view when a prefix is used'
},
{
query: ':group',
expectedMatches: {},
reason: 'Finds no results if using namespaces prefix (:) to search for filter title'
}
],
model = new rcfilters.dm.FiltersViewModel(),
extractNames = function ( matches ) {
const result = {};
for ( const groupName in matches ) {
result[ groupName ] = matches[ groupName ].map( ( item ) => item.getName() );
}
return result;
};
model.initializeFilters( filterDefinition, viewsDefinition );
testCases.forEach( ( testCase ) => {
const matches = model.findMatches( testCase.query );
assert.deepEqual(
extractNames( matches ),
testCase.expectedMatches,
testCase.reason
);
} );
const foundMatches = model.findMatches( 'foo' );
assert.true(
$.isEmptyObject( foundMatches ),
'findMatches returns an empty object when no results found'
);
} );
QUnit.test( 'getParametersFromFilters', ( assert ) => {
let model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
// Starting with all filters unselected
assert.deepEqual(
model.getParametersFromFilters(),
baseParamRepresentation,
'Unselected filters return all parameters falsey or \'\'.'
);
// Select 1 filter
model.toggleFiltersSelected( {
group1__filter1: true
} );
// Only one filter in one group
assert.deepEqual(
model.getParametersFromFilters(),
$.extend( true, {}, baseParamRepresentation, {
// Group 1 (one selected, the others are true)
filter2: '1',
filter3: '1'
} ),
'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
);
// Select 2 filters
model.toggleFiltersSelected( {
group1__filter1: true,
group1__filter2: true
} );
// Two selected filters in one group
assert.deepEqual(
model.getParametersFromFilters(),
$.extend( true, {}, baseParamRepresentation, {
// Group 1 (two selected, the other is true)
filter3: '1'
} ),
'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
);
// Select 3 filters
model.toggleFiltersSelected( {
group1__filter1: true,
group1__filter2: true,
group1__filter3: true
} );
// All filters of the group are selected == this is the same as not selecting any
assert.deepEqual(
model.getParametersFromFilters(),
baseParamRepresentation,
'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
);
// Select 1 filter from string_options
model.toggleFiltersSelected( {
group3__filter7: true,
group3__filter8: false,
group3__filter9: false
} );
// All filters of the group are selected == this is the same as not selecting any
assert.deepEqual(
model.getParametersFromFilters(),
$.extend( true, {}, baseParamRepresentation, {
group3: 'filter7'
} ),
'One filter selected in "string_option" group returns that filter in the value.'
);
// Select 2 filters from string_options
model.toggleFiltersSelected( {
group3__filter7: true,
group3__filter8: true,
group3__filter9: false
} );
// All filters of the group are selected == this is the same as not selecting any
assert.deepEqual(
model.getParametersFromFilters(),
$.extend( true, {}, baseParamRepresentation, {
group3: 'filter7,filter8'
} ),
'Two filters selected in "string_option" group returns those filters in the value.'
);
// Select 3 filters from string_options
model.toggleFiltersSelected( {
group3__filter7: true,
group3__filter8: true,
group3__filter9: true
} );
// All filters of the group are selected == this is the same as not selecting any
assert.deepEqual(
model.getParametersFromFilters(),
$.extend( true, {}, baseParamRepresentation, {
group3: 'all'
} ),
'All filters selected in "string_option" group returns \'all\'.'
);
// Reset
model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
// Select an option from single_option group
model.toggleFiltersSelected( {
group4__option2: true
} );
// All filters of the group are selected == this is the same as not selecting any
assert.deepEqual(
model.getParametersFromFilters(),
$.extend( true, {}, baseParamRepresentation, {
group4: 'option2'
} ),
'Selecting an option from "single_option" group returns that option as a value.'
);
// Select a different option from single_option group
model.toggleFiltersSelected( {
group4__option3: true
} );
// All filters of the group are selected == this is the same as not selecting any
assert.deepEqual(
model.getParametersFromFilters(),
$.extend( true, {}, baseParamRepresentation, {
group4: 'option3'
} ),
'Selecting a different option from "single_option" group changes the selection.'
);
} );
QUnit.test( 'getParametersFromFilters (custom object)', ( assert ) => {
// This entire test uses different base definition than the global one
// on purpose, to verify that the values inserted as a custom object
// are the ones we expect in return
const model = new rcfilters.dm.FiltersViewModel(),
definition = [ {
name: 'group1',
title: 'Group 1',
type: 'send_unselected_if_any',
filters: [
{ name: 'hidefilter1', label: 'Hide filter 1', description: '' },
{ name: 'hidefilter2', label: 'Hide filter 2', description: '' },
{ name: 'hidefilter3', label: 'Hide filter 3', description: '' }
]
}, {
name: 'group2',
title: 'Group 2',
type: 'send_unselected_if_any',
filters: [
{ name: 'hidefilter4', label: 'Hide filter 4', description: '' },
{ name: 'hidefilter5', label: 'Hide filter 5', description: '' },
{ name: 'hidefilter6', label: 'Hide filter 6', description: '' }
]
}, {
name: 'group3',
title: 'Group 3',
type: 'string_options',
separator: ',',
filters: [
{ name: 'filter7', label: 'Hide filter 7', description: '' },
{ name: 'filter8', label: 'Hide filter 8', description: '' },
{ name: 'filter9', label: 'Hide filter 9', description: '' }
]
}, {
name: 'group4',
title: 'Group 4',
type: 'single_option',
filters: [
{ name: 'filter10', label: 'Hide filter 10', description: '' },
{ name: 'filter11', label: 'Hide filter 11', description: '' },
{ name: 'filter12', label: 'Hide filter 12', description: '' }
]
} ],
baseResult = {
hidefilter1: '0',
hidefilter2: '0',
hidefilter3: '0',
hidefilter4: '0',
hidefilter5: '0',
hidefilter6: '0',
group3: '',
group4: ''
},
cases = [
{
// This is mocking the cases above, both
// - 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
// - 'Two filters selected in "string_option" group returns those filters in the value.'
input: {
group1__hidefilter1: true,
group1__hidefilter2: true,
group1__hidefilter3: false,
group2__hidefilter4: false,
group2__hidefilter5: false,
group2__hidefilter6: false,
group3__filter7: true,
group3__filter8: true,
group3__filter9: false
},
expected: $.extend( true, {}, baseResult, {
// Group 1 (two selected, the others are true)
hidefilter3: '1',
// Group 3 (two selected)
group3: 'filter7,filter8'
} ),
msg: 'Given an explicit (complete) filter state object, the result is the same as if the object given represented the model state.'
},
{
// This is mocking case above
// - 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
input: {
group1__hidefilter1: 1
},
expected: $.extend( true, {}, baseResult, {
// Group 1 (one selected, the others are true)
hidefilter2: '1',
hidefilter3: '1'
} ),
msg: 'Given an explicit (incomplete) filter state object, the result is the same as if the object give represented the model state.'
},
{
input: {
group4__filter10: true
},
expected: $.extend( true, {}, baseResult, {
group4: 'filter10'
} ),
msg: 'Given a single value for "single_option" that option is represented in the result.'
},
{
input: {
group4__filter10: true,
group4__filter11: true
},
expected: $.extend( true, {}, baseResult, {
group4: 'filter10'
} ),
msg: 'Given more than one true value for "single_option" (which should not happen!) only the first value counts, and the second is ignored.'
},
{
input: {},
expected: baseResult,
msg: 'Given an explicit empty object, the result is all filters set to their falsey unselected value.'
}
];
model.initializeFilters( definition );
// Store original state
const originalState = model.getSelectedState();
// Test each case
cases.forEach( ( test ) => {
assert.deepEqual(
model.getParametersFromFilters( test.input ),
test.expected,
test.msg
);
} );
// After doing the above tests, make sure the actual state
// of the filter stayed the same
assert.deepEqual(
model.getSelectedState(),
originalState,
'Running the method with external definition to parse does not actually change the state of the model'
);
} );
QUnit.test( 'getFiltersFromParameters', ( assert ) => {
let model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
// Empty query = only default values
assert.deepEqual(
model.getFiltersFromParameters( {} ),
baseFilterRepresentation,
'Empty parameter query results in an object representing all filters set to their base state'
);
assert.deepEqual(
model.getFiltersFromParameters( {
filter2: '1'
} ),
Object.assign( {}, baseFilterRepresentation, {
group1__filter1: true, // The text is "show filter 1"
group1__filter2: false, // The text is "show filter 2"
group1__filter3: true // The text is "show filter 3"
} ),
'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
);
assert.deepEqual(
model.getFiltersFromParameters( {
filter1: '1',
filter2: '1',
filter3: '1'
} ),
Object.assign( {}, baseFilterRepresentation, {
group1__filter1: false, // The text is "show filter 1"
group1__filter2: false, // The text is "show filter 2"
group1__filter3: false // The text is "show filter 3"
} ),
'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
);
// The ones above don't update the model, so we have a clean state.
// getFiltersFromParameters is stateless; any change is unaffected by the current state
// This test is demonstrating wrong usage of the method;
// We should be aware that getFiltersFromParameters is stateless,
// so each call gives us a filter state that only reflects the query given.
// This means that the two calls to toggleFiltersSelected() below collide.
// The result of the first is overridden by the result of the second,
// since both get a full state object from getFiltersFromParameters that **only** relates
// to the input it receives.
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
filter1: '1'
} )
);
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
filter6: '1'
} )
);
// The result here is ignoring the first toggleFiltersSelected call
assert.deepEqual(
model.getSelectedState(),
Object.assign( {}, baseFilterRepresentation, {
group2__filter4: true,
group2__filter5: true,
group2__filter6: false
} ),
'getFiltersFromParameters does not care about previous or existing state.'
);
// Reset
model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7'
} )
);
assert.deepEqual(
model.getSelectedState(),
Object.assign( {}, baseFilterRepresentation, {
group3__filter7: true,
group3__filter8: false,
group3__filter9: false
} ),
'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
);
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7,filter8'
} )
);
assert.deepEqual(
model.getSelectedState(),
Object.assign( {}, baseFilterRepresentation, {
group3__filter7: true,
group3__filter8: true,
group3__filter9: false
} ),
'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
);
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7,filter8,filter9'
} )
);
assert.deepEqual(
model.getSelectedState(),
Object.assign( {}, baseFilterRepresentation, {
group3__filter7: true,
group3__filter8: true,
group3__filter9: true
} ),
'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
);
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7,all,filter9'
} )
);
assert.deepEqual(
model.getSelectedState(),
Object.assign( {}, baseFilterRepresentation, {
group3__filter7: true,
group3__filter8: true,
group3__filter9: true
} ),
'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
);
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7,foo,filter9'
} )
);
assert.deepEqual(
model.getSelectedState(),
Object.assign( {}, baseFilterRepresentation, {
group3__filter7: true,
group3__filter8: false,
group3__filter9: true
} ),
'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
);
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group4: 'option1'
} )
);
assert.deepEqual(
model.getSelectedState(),
Object.assign( {}, baseFilterRepresentation, {
group4__option1: true,
group4__option2: false
} ),
'A \'single_option\' parameter reflects a single selected value.'
);
assert.deepEqual(
model.getFiltersFromParameters( {
group4: 'option1,option2'
} ),
baseFilterRepresentation,
'An invalid \'single_option\' parameter is ignored.'
);
// Change to one value
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group4: 'option1'
} )
);
// Change again to another value
model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group4: 'option2'
} )
);
assert.deepEqual(
model.getSelectedState(),
Object.assign( {}, baseFilterRepresentation, {
group4__option2: true
} ),
'A \'single_option\' parameter always reflects the latest selected value.'
);
} );
QUnit.test( 'sanitizeStringOptionGroup', ( assert ) => {
const model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
assert.deepEqual(
model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
[ 'filter1', 'filter2' ],
'Remove duplicate values'
);
assert.deepEqual(
model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
[ 'filter1', 'filter2' ],
'Remove invalid values'
);
assert.deepEqual(
model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
[ 'all' ],
'If any value is "all", the only value is "all".'
);
} );
QUnit.test( 'Filter interaction: subsets', ( assert ) => {
const model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
// Select a filter that has subset with another filter
model.toggleFiltersSelected( {
group1__filter1: true
} );
model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter1: { selected: true },
group1__filter2: { included: true },
group1__filter3: { included: true },
// Conflicts are affected
group2__filter4: { conflicted: true },
group2__filter5: { conflicted: true },
group2__filter6: { conflicted: true }
} ),
'Filters with subsets are represented in the model.'
);
// Select another filter that has a subset with the same previous filter
model.toggleFiltersSelected( {
group1__filter2: true
} );
model.reassessFilterInteractions( model.getItemByName( 'filter2' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter1: { selected: true },
group1__filter2: { selected: true, included: true },
group1__filter3: { included: true },
// Conflicts are affected
group2__filter6: { conflicted: true }
} ),
'Filters that have multiple subsets are represented.'
);
// Remove one filter (but leave the other) that affects filter3
model.toggleFiltersSelected( {
group1__filter1: false
} );
model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter2: { selected: true, included: false },
group1__filter3: { included: true },
// Conflicts are affected
group2__filter6: { conflicted: true }
} ),
'Removing a filter only un-includes its subset if there is no other filter affecting.'
);
model.toggleFiltersSelected( {
group1__filter2: false
} );
model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) );
assert.deepEqual(
model.getFullState(),
baseFullFilterState,
'Removing all supersets also un-includes the subsets.'
);
} );
QUnit.test( 'Filter interaction: full coverage', ( assert ) => {
const model = new rcfilters.dm.FiltersViewModel(),
isCapsuleItemMuted = function ( filterName ) {
const itemModel = model.getItemByName( filterName ),
groupModel = itemModel.getGroupModel();
// This is the logic inside the capsule widget
return (
// The capsule item widget only appears if the item is selected
itemModel.isSelected() &&
// Muted state is only valid if group is full coverage and all items are selected
groupModel.isFullCoverage() && groupModel.areAllSelected()
);
},
getCurrentItemsMutedState = function () {
return {
group1__filter1: isCapsuleItemMuted( 'group1__filter1' ),
group1__filter2: isCapsuleItemMuted( 'group1__filter2' ),
group1__filter3: isCapsuleItemMuted( 'group1__filter3' ),
group2__filter4: isCapsuleItemMuted( 'group2__filter4' ),
group2__filter5: isCapsuleItemMuted( 'group2__filter5' ),
group2__filter6: isCapsuleItemMuted( 'group2__filter6' )
};
},
baseMuteState = {
group1__filter1: false,
group1__filter2: false,
group1__filter3: false,
group2__filter4: false,
group2__filter5: false,
group2__filter6: false
};
model.initializeFilters( filterDefinition, viewsDefinition );
// Starting state, no selection, all items are non-muted
assert.deepEqual(
getCurrentItemsMutedState(),
baseMuteState,
'No selection - all items are non-muted'
);
// Select most (but not all) items in each group
model.toggleFiltersSelected( {
group1__filter1: true,
group1__filter2: true,
group2__filter4: true,
group2__filter5: true
} );
// Both groups have multiple (but not all) items selected, all items are non-muted
assert.deepEqual(
getCurrentItemsMutedState(),
baseMuteState,
'Not all items in the group selected - all items are non-muted'
);
// Select all items in 'fullCoverage' group (group2)
model.toggleFiltersSelected( {
group2__filter6: true
} );
// Group2 (full coverage) has all items selected, all its items are muted
assert.deepEqual(
getCurrentItemsMutedState(),
Object.assign( {}, baseMuteState, {
group2__filter4: true,
group2__filter5: true,
group2__filter6: true
} ),
'All items in \'full coverage\' group are selected - all items in the group are muted'
);
// Select all items in non 'fullCoverage' group (group1)
model.toggleFiltersSelected( {
group1__filter3: true
} );
// Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
assert.deepEqual(
getCurrentItemsMutedState(),
Object.assign( {}, baseMuteState, {
group2__filter4: true,
group2__filter5: true,
group2__filter6: true
} ),
'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
);
// Uncheck an item from each group
model.toggleFiltersSelected( {
group1__filter3: false,
group2__filter5: false
} );
assert.deepEqual(
getCurrentItemsMutedState(),
baseMuteState,
'Not all items in the group are checked - all items are non-muted regardless of group coverage'
);
} );
QUnit.test( 'Filter interaction: conflicts', ( assert ) => {
let model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
assert.deepEqual(
model.getFullState(),
baseFullFilterState,
'Initial state: no conflicts because no selections.'
);
// Select a filter that has a conflict with an entire group
model.toggleFiltersSelected( {
group1__filter1: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
} );
model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter1: { selected: true },
group2__filter4: { conflicted: true },
group2__filter5: { conflicted: true },
group2__filter6: { conflicted: true },
// Subsets are affected by the selection
group1__filter2: { included: true },
group1__filter3: { included: true }
} ),
'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
);
// Select one of the conflicts (both filters are now conflicted and selected)
model.toggleFiltersSelected( {
group2__filter4: true // conflicts: filter 1
} );
model.reassessFilterInteractions( model.getItemByName( 'group2__filter4' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter1: { selected: true, conflicted: true },
group2__filter4: { selected: true, conflicted: true },
group2__filter5: { conflicted: true },
group2__filter6: { conflicted: true },
// Subsets are affected by the selection
group1__filter2: { included: true },
group1__filter3: { included: true }
} ),
'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
);
// Reset
model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
// Select a filter that has a conflict with a specific filter
model.toggleFiltersSelected( {
group1__filter2: true // conflicts: filter6
} );
model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter2: { selected: true },
group2__filter6: { conflicted: true },
// Subsets are affected by the selection
group1__filter3: { included: true }
} ),
'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
);
// Select the conflicting filter
model.toggleFiltersSelected( {
group2__filter6: true // conflicts: filter2
} );
model.reassessFilterInteractions( model.getItemByName( 'group2__filter6' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter2: { selected: true, conflicted: true },
group2__filter6: { selected: true, conflicted: true },
// This is added to the conflicts because filter6 is part of group2,
// who is in conflict with filter1; note that filter2 also conflicts
// with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
// and also because its **own sibling** (filter2) is **also** in conflict with the
// selected items in group2 (filter6)
group1__filter1: { conflicted: true },
// Subsets are affected by the selection
group1__filter3: { included: true }
} ),
'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
);
// Now choose a non-conflicting filter from the group
model.toggleFiltersSelected( {
group2__filter5: true
} );
model.reassessFilterInteractions( model.getItemByName( 'group2__filter5' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter2: { selected: true },
group2__filter6: { selected: true },
group2__filter5: { selected: true },
// Filter6 and filter1 are no longer in conflict because
// filter5, while it is in conflict with filter1, it is
// not in conflict with filter2 - and since filter2 is
// selected, it removes the conflict bidirectionally
// Subsets are affected by the selection
group1__filter3: { included: true }
} ),
'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
);
// Followup on the previous test, unselect filter2 so filter1
// is now the only one selected in its own group, and since
// it is in conflict with the entire of group2, it means
// filter1 is once again conflicted
model.toggleFiltersSelected( {
group1__filter2: false
} );
model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter1: { conflicted: true },
group2__filter6: { selected: true },
group2__filter5: { selected: true }
} ),
'Unselecting an item that did not conflict returns the conflict state.'
);
// Followup #2: Now actually select filter1, and make everything conflicted
model.toggleFiltersSelected( {
group1__filter1: true
} );
model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter1: { selected: true, conflicted: true },
group2__filter6: { selected: true, conflicted: true },
group2__filter5: { selected: true, conflicted: true },
group2__filter4: { conflicted: true }, // Not selected but conflicted because it's in group2
// Subsets are affected by the selection
group1__filter2: { included: true },
group1__filter3: { included: true }
} ),
'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
);
/* Simple case */
// Reset
model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( filterDefinition, viewsDefinition );
// Select a filter that has a conflict with a specific filter
model.toggleFiltersSelected( {
group1__filter2: true // conflicts: filter6
} );
model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter2: { selected: true },
group2__filter6: { conflicted: true },
// Subsets are affected by the selection
group1__filter3: { included: true }
} ),
'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
);
model.toggleFiltersSelected( {
group1__filter3: true // conflicts: filter6
} );
model.reassessFilterInteractions( model.getItemByName( 'group1__filter3' ) );
assert.deepEqual(
model.getFullState(),
$.extend( true, {}, baseFullFilterState, {
group1__filter2: { selected: true },
// Subsets are affected by the selection
group1__filter3: { selected: true, included: true }
} ),
'Simple case: Selecting a filter that is not in conflict removes the conflict.'
);
} );
QUnit.test( 'Filter highlights', ( assert ) => {
// We are using a different (smaller) definition here than the global one
const definition = [ {
name: 'group1',
title: 'Group 1',
type: 'string_options',
filters: [
{ name: 'filter1', cssClass: 'class1', label: '1', description: '1' },
{ name: 'filter2', cssClass: 'class2', label: '2', description: '2' },
{ name: 'filter3', cssClass: 'class3', label: '3', description: '3' },
{ name: 'filter4', cssClass: 'class4', label: '4', description: '4' },
{ name: 'filter5', cssClass: 'class5', label: '5', description: '5' },
{ name: 'filter6', label: '6', description: '6' }
]
} ];
let model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( definition );
assert.strictEqual(
model.isHighlightEnabled(),
false,
'Initially, highlight is disabled.'
);
model.toggleHighlight( true );
assert.strictEqual(
model.isHighlightEnabled(),
true,
'Highlight is enabled on toggle.'
);
model.setHighlightColor( 'group1__filter1', 'color1' );
model.setHighlightColor( 'group1__filter2', 'color2' );
assert.deepEqual(
model.getHighlightedItems().map( ( item ) => item.getName() ),
[
'group1__filter1',
'group1__filter2'
],
'Highlighted items are highlighted.'
);
assert.strictEqual(
model.getItemByName( 'group1__filter1' ).getHighlightColor(),
'color1',
'Item highlight color is set.'
);
model.setHighlightColor( 'group1__filter1', 'color1changed' );
assert.strictEqual(
model.getItemByName( 'group1__filter1' ).getHighlightColor(),
'color1changed',
'Item highlight color is changed on setHighlightColor.'
);
model.clearHighlightColor( 'group1__filter1' );
assert.deepEqual(
model.getHighlightedItems().map( ( item ) => item.getName() ),
[
'group1__filter2'
],
'Clear highlight from an item results in the item no longer being highlighted.'
);
// Reset
model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( definition );
model.setHighlightColor( 'group1__filter1', 'color1' );
model.setHighlightColor( 'group1__filter2', 'color2' );
model.setHighlightColor( 'group1__filter3', 'color3' );
assert.deepEqual(
model.getHighlightedItems().map( ( item ) => item.getName() ),
[
'group1__filter1',
'group1__filter2',
'group1__filter3'
],
'Even if highlights are not enabled, the items remember their highlight state'
// NOTE: When actually displaying the highlights, the UI checks whether
// highlighting is generally active and then goes over the highlighted
// items. The item models, however, and the view model in general, still
// retains the knowledge about which filters have different colors, so we
// can seamlessly return to the colors the user previously chose if they
// reapply highlights.
);
// Reset
model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( definition );
model.setHighlightColor( 'group1__filter1', 'color1' );
model.setHighlightColor( 'group1__filter6', 'color6' );
assert.deepEqual(
model.getHighlightedItems().map( ( item ) => item.getName() ),
[
'group1__filter1'
],
'Items without a specified class identifier are not highlighted.'
);
} );
QUnit.test( 'emptyAllFilters', ( assert ) => {
const model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( shortFilterDefinition, null );
model.toggleFiltersSelected( {
group1__filter1: true,
group2__filter4: true, // hidden
group3__filter5: true // sticky
} );
model.emptyAllFilters();
assert.deepEqual(
model.getSelectedState( true ),
{
group3__filter5: true,
group3__filter6: true
},
'Emptying filters does not affect sticky filters'
);
} );
QUnit.test( 'areVisibleFiltersEmpty', ( assert ) => {
const model = new rcfilters.dm.FiltersViewModel();
model.initializeFilters( shortFilterDefinition, null );
model.emptyAllFilters();
assert.strictEqual( model.areVisibleFiltersEmpty(), true );
model.toggleFiltersSelected( {
group3__filter5: true // sticky
} );
assert.strictEqual( model.areVisibleFiltersEmpty(), true );
model.toggleFiltersSelected( {
group1__filter1: true
} );
assert.strictEqual( model.areVisibleFiltersEmpty(), false );
} );
}() );

File Metadata

Mime Type
text/plain
Expires
Wed, Aug 19, 00:46 (6 d, 6 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
3c/90/d8608b7ef289e12c604f3b05c21e
Default Alt Text
dm.FiltersViewModel.test.js (48 KB)

Event Timeline