summaryrefslogtreecommitdiff
path: root/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js')
-rw-r--r--www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js1562
1 files changed, 1562 insertions, 0 deletions
diff --git a/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js
new file mode 100644
index 00000000..2b42b5ab
--- /dev/null
+++ b/www/wiki/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js
@@ -0,0 +1,1562 @@
+/* eslint-disable camelcase */
+( function ( mw, $ ) {
+ var 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', function ( assert ) {
+ var model = new mw.rcfilters.dm.FiltersViewModel();
+
+ model.initializeFilters( filterDefinition, viewsDefinition );
+
+ // Test that all items were created
+ assert.ok(
+ Object.keys( baseFilterRepresentation ).every( function ( filterName ) {
+ return model.getItemByName( filterName ) instanceof mw.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', function ( assert ) {
+ var model = new mw.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', function ( assert ) {
+ var model = new mw.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( function ( test ) {
+ assert.deepEqual(
+ model.getMinimizedParamRepresentation( test.input ),
+ test.result,
+ test.msg
+ );
+ } );
+ } );
+
+ QUnit.test( 'Parameter states', function ( 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:
+ var parametersWithoutExcluded,
+ appliedDefaultParameters = {
+ group4: 'option2',
+ group5: 'option1',
+ // Sticky, their defaults apply immediately
+ group6option2: '1',
+ group6option3: '1',
+ group7: 'group7option2'
+ },
+ model = new mw.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 mw.rcfilters.dm.FiltersViewModel();
+ model.initializeFilters( filterDefinition, viewsDefinition );
+
+ 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', function ( assert ) {
+ var model = new mw.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( function ( test ) {
+ assert.deepEqual(
+ model.removeStickyParams( test.input ),
+ test.result,
+ test.msg
+ );
+ } );
+
+ } );
+
+ QUnit.test( 'Finding matching filters', function ( assert ) {
+ var matches,
+ 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 mw.rcfilters.dm.FiltersViewModel(),
+ extractNames = function ( matches ) {
+ var result = {};
+ Object.keys( matches ).forEach( function ( groupName ) {
+ result[ groupName ] = matches[ groupName ].map( function ( item ) {
+ return item.getName();
+ } );
+ } );
+ return result;
+ };
+
+ model.initializeFilters( filterDefinition, viewsDefinition );
+
+ testCases.forEach( function ( testCase ) {
+ matches = model.findMatches( testCase.query );
+ assert.deepEqual(
+ extractNames( matches ),
+ testCase.expectedMatches,
+ testCase.reason
+ );
+ } );
+
+ matches = model.findMatches( 'foo' );
+ assert.ok(
+ $.isEmptyObject( matches ),
+ 'findMatches returns an empty object when no results found'
+ );
+ } );
+
+ QUnit.test( 'getParametersFromFilters', function ( assert ) {
+ var model = new mw.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 mw.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)', function ( 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
+ var originalState,
+ model = new mw.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
+ originalState = model.getSelectedState();
+
+ // Test each case
+ cases.forEach( function ( 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', function ( assert ) {
+ var model = new mw.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'
+ } ),
+ $.extend( {}, 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'
+ } ),
+ $.extend( {}, 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(),
+ $.extend( {}, baseFilterRepresentation, {
+ group2__filter4: true,
+ group2__filter5: true,
+ group2__filter6: false
+ } ),
+ 'getFiltersFromParameters does not care about previous or existing state.'
+ );
+
+ // Reset
+ model = new mw.rcfilters.dm.FiltersViewModel();
+ model.initializeFilters( filterDefinition, viewsDefinition );
+
+ model.toggleFiltersSelected(
+ model.getFiltersFromParameters( {
+ group3: 'filter7'
+ } )
+ );
+ assert.deepEqual(
+ model.getSelectedState(),
+ $.extend( {}, 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(),
+ $.extend( {}, 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(),
+ $.extend( {}, 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(),
+ $.extend( {}, 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(),
+ $.extend( {}, 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(),
+ $.extend( {}, 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(),
+ $.extend( {}, baseFilterRepresentation, {
+ group4__option2: true
+ } ),
+ 'A \'single_option\' parameter always reflects the latest selected value.'
+ );
+ } );
+
+ QUnit.test( 'sanitizeStringOptionGroup', function ( assert ) {
+ var model = new mw.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', function ( assert ) {
+ var model = new mw.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', function ( assert ) {
+ var model = new mw.rcfilters.dm.FiltersViewModel(),
+ isCapsuleItemMuted = function ( filterName ) {
+ var 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(),
+ $.extend( {}, 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(),
+ $.extend( {}, 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', function ( assert ) {
+ var model = new mw.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 mw.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 mw.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', function ( assert ) {
+ // We are using a different (smaller) definition here than the global one
+ var 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' }
+ ]
+ } ],
+ model = new mw.rcfilters.dm.FiltersViewModel();
+
+ model.initializeFilters( definition );
+
+ assert.ok(
+ !model.isHighlightEnabled(),
+ 'Initially, highlight is disabled.'
+ );
+
+ model.toggleHighlight( true );
+ assert.ok(
+ model.isHighlightEnabled(),
+ 'Highlight is enabled on toggle.'
+ );
+
+ model.setHighlightColor( 'group1__filter1', 'color1' );
+ model.setHighlightColor( 'group1__filter2', 'color2' );
+
+ assert.deepEqual(
+ model.getHighlightedItems().map( function ( item ) {
+ return item.getName();
+ } ),
+ [
+ 'group1__filter1',
+ 'group1__filter2'
+ ],
+ 'Highlighted items are highlighted.'
+ );
+
+ assert.equal(
+ model.getItemByName( 'group1__filter1' ).getHighlightColor(),
+ 'color1',
+ 'Item highlight color is set.'
+ );
+
+ model.setHighlightColor( 'group1__filter1', 'color1changed' );
+ assert.equal(
+ model.getItemByName( 'group1__filter1' ).getHighlightColor(),
+ 'color1changed',
+ 'Item highlight color is changed on setHighlightColor.'
+ );
+
+ model.clearHighlightColor( 'group1__filter1' );
+ assert.deepEqual(
+ model.getHighlightedItems().map( function ( item ) {
+ return item.getName();
+ } ),
+ [
+ 'group1__filter2'
+ ],
+ 'Clear highlight from an item results in the item no longer being highlighted.'
+ );
+
+ // Reset
+ model = new mw.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( function ( item ) {
+ return 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 mw.rcfilters.dm.FiltersViewModel();
+ model.initializeFilters( definition );
+
+ model.setHighlightColor( 'group1__filter1', 'color1' );
+ model.setHighlightColor( 'group1__filter6', 'color6' );
+
+ assert.deepEqual(
+ model.getHighlightedItems().map( function ( item ) {
+ return item.getName();
+ } ),
+ [
+ 'group1__filter1'
+ ],
+ 'Items without a specified class identifier are not highlighted.'
+ );
+ } );
+
+ QUnit.test( 'emptyAllFilters', function ( assert ) {
+ var model = new mw.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', function ( assert ) {
+ var model = new mw.rcfilters.dm.FiltersViewModel();
+ model.initializeFilters( shortFilterDefinition, null );
+
+ model.emptyAllFilters();
+ assert.ok( model.areVisibleFiltersEmpty() );
+
+ model.toggleFiltersSelected( {
+ group3__filter5: true // sticky
+ } );
+ assert.ok( model.areVisibleFiltersEmpty() );
+
+ model.toggleFiltersSelected( {
+ group1__filter1: true
+ } );
+ assert.notOk( model.areVisibleFiltersEmpty() );
+ } );
+}( mediaWiki, jQuery ) );