diff --git a/test/nbrowser/ColumnFilterMenu.ts b/test/nbrowser/ColumnFilterMenu.ts new file mode 100644 index 00000000..863aef5a --- /dev/null +++ b/test/nbrowser/ColumnFilterMenu.ts @@ -0,0 +1,621 @@ +import { UserAPI } from 'app/common/UserAPI'; +import { addToRepl, assert, driver, Key } from 'mocha-webdriver'; +import * as gu from 'test/nbrowser/gristUtils'; +import { setupTestSuite } from 'test/nbrowser/testUtils'; + +const limitShown = 500; + +// Sum all of the counts directly on the browser using `driver.executeScript(...)`. There could me +// over 500 of them and using the classic driver.findAll(...) approach makes it too slow and causes +// the test to crash (timeout). +function getCount() { + return driver.executeScript(` + return Array.from(document.querySelectorAll('.test-filter-menu-count'), e => e.innerText) + .map(s => s.split(',').join('')) + .map(Number) + .reduce((acc, v) => acc + v, 0); +`); +} + +// find a filter value by name +function findByName(regex: RegExp | string) { + return driver.findContent('.test-filter-menu-list label', regex); +} + +describe('ColumnFilterMenu', function() { + this.timeout(20000); + const cleanup = setupTestSuite(); + addToRepl('findByName', findByName); + let doc: any; + let api: UserAPI; + + it('should handle empty lists consistently', async function() { + // A formula returning an empty RecordSet in a RefList columns results in storing [] instead of null. + // This previously caused a bug where the empty list was 'flattened' and the cell not appearing in filters at all. + const session = await gu.session().teamSite.login(); + const api = session.createHomeApi(); + const docId = await session.tempNewDoc(cleanup, 'FilterEmptyLists', {load: false}); + + await api.applyUserActions(docId, [ + ['AddTable', 'Table2', [ + { + id: 'A', type: 'RefList:Table2', isFormula: true, + // This means that the first cell will contain [] while the second will contain null. + // The test asserts that both end up being treated the same. + formula: 'if $id == 1: return table.lookupRecords(B="foobar")' + }, + {id: 'B'}, + ]], + ['BulkAddRecord', 'Table2', [null, null], {B: [1, 2]}], + ]); + + await session.loadDoc(`/doc/${docId}/p/2`); + + await gu.rightClick(gu.getCell({rowNum: 1, col: 'A'})); + await driver.findContent('.grist-floating-menu li', 'Filter by this value').click(); + + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['A', 'B'], rowNums: [1, 2, 3]}), + [ + '', '1', + '', '2', + '', '' + ] + ); + + await gu.openColumnMenu('A', 'Filter'); + + assert.deepEqual( + await driver.findAll('.test-filter-menu-list .test-filter-menu-count', (e) => e.getText()), + ['2'], + ); + }); + + it('should show only first 500', async function() { + const session = await gu.session().teamSite.login(); + await session.tempDoc(cleanup, 'World.grist'); + + // check row count is > 4000 + const total = await gu.getGridRowCount() - 1; + assert.equal(total, 4079); + + // scroll back to top + await gu.sendKeys(Key.chord(await gu.modKey(), Key.UP)); + + // open filter menu for first column + await gu.openColumnMenu('Name', 'Filter'); + + // check ther are 500 entry shown + assert.lengthOf(await driver.findAll('.test-filter-menu-list label'), limitShown); + + // check `Other` summary is present + assert.deepEqual( + await driver.findAll('.test-filter-menu-summary', (e) => e.find('label').getText()), + ['Other Values (3,501)', 'Future Values'] + ); + + // check counts add up + assert.equal(await getCount(), total); + + // type 'A' to search + await gu.sendKeys('A'); + + // check summary has `Other matching` and Other Non-matching` + assert.deepEqual( + await driver.findAll('.test-filter-menu-summary', (e) => e.find('label').getText()), + ['Other Matching (2,493)', 'Other Non-Matching (1,008)'] + ); + + // check count adds up + assert.equal(await getCount(), total); + + // clear search input + await gu.sendKeys(Key.BACK_SPACE); + + // Click All Except / Other Matching / Other NOn-Matching + await driver.findContent('.test-filter-menu-bulk-action', /None/).click(); + + // click Aba and Abadan + await driver.findContent('.test-filter-menu-list label', /Aba/).click(); + await driver.findContent('.test-filter-menu-list label', /Abadan/).click(); + + // Apply filter + await driver.find('.test-filter-menu-apply-btn').click(); + + // check grid contains aba and abadan + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name'], rowNums: [1, 2, 3]}), + [ + 'Aba', + 'Abadan', + '' + ] + ); + + }); + + it('should uncheck \'Other Values\' checkbox when user clicks \'None\'', async () => { + // open the Name filter + await gu.openColumnMenu('Name', 'Filter'); + + // click None + await driver.findContent('.test-filter-menu-bulk-action', /None/).click(); + + // check Other values was propertly unchecked + assert.equal( + await driver.findContent('.test-filter-menu-summary', /Other Values/).find('input').matches(':checked'), + false + ); + + assert.equal( + await driver.findContent('.test-filter-menu-summary', /Future Values/).find('input').matches(':checked'), + false + ); + }); + + it('should take other filters into account', async () => { + + const session = await gu.session().teamSite.login(); + doc = await session.tempDoc(cleanup, 'SortFilterIconTest.grist'); + api = session.createHomeApi(); + + // check table content + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', 'Count'], rowNums: [1, 2, 3, 4, 5, 6]}), + [ 'Apples', '1', + 'Oranges', '3', + 'Bananas', '2', + 'Grapes', '-1', + 'Grapefruit', 'n/a', + 'Clementines', '5' + ]); + + // add Name Filter + await gu.openColumnMenu('Name', 'Filter'); + + // Click Oranges + await findByName('Oranges').click(); + + // Click Apply + await driver.find('.test-filter-menu-apply-btn').click(); + + // add Count filters + await driver.find('.test-add-filter-btn').click(); + await driver.findContent('.grist-floating-menu li', /Count/).click(); + + // Check that there's only 5 values left ('3' is missing) + assert.deepEqual(await driver.findAll('.test-filter-menu-list label', (e) => e.getText()), + ['n/a', '-1', '1', '2', '5']); + + // Check `Others` shows unique count + assert.equal(await driver.find('.test-filter-menu-summary').getText(), + 'Others (1)'); + + // Check `Others` is checked + assert.equal(await driver.find('.test-filter-menu-summary').find('input').matches(':checked'), true); + + // Click `Other` + await driver.find('.test-filter-menu-summary').find('input').click(); + + // Click '1' + await findByName(/^1/).click(); + + // Click Apply + await driver.find('.test-filter-menu-apply-btn').click(); + + // Open the Name menu filter + await driver.findContent('.test-filter-field', /Name/).click(); + + // Check there's only 4 values left + assert.deepEqual(await driver.findAll('.test-filter-menu-list label', (e) => e.getText()), + ['Bananas', 'Clementines', 'Grapefruit', 'Grapes']); + + // check `Others` shows 2 unique values + assert.equal(await driver.find('.test-filter-menu-summary').getText(), + 'Others (2)'); + + // check `Others` is in indeterminate state + assert.equal(await driver.find('.test-filter-menu-summary').find('input').matches(':checked'), false); + assert.equal(await driver.find('.test-filter-menu-summary').find('input').matches(':indeterminate'), true); + + // Click `Others` + await driver.find('.test-filter-menu-summary').find('input').click(); + + // check `Others` is checked + assert.equal(await driver.find('.test-filter-menu-summary').find('input').matches(':checked'), true); + assert.equal(await driver.find('.test-filter-menu-summary').find('input').matches(':indeterminate'), false); + + // Click `Others` + await driver.find('.test-filter-menu-summary').find('input').click(); + + // check `Others` is checked + assert.equal(await driver.find('.test-filter-menu-summary').find('input').matches(':checked'), false); + assert.equal(await driver.find('.test-filter-menu-summary').find('input').matches(':indeterminate'), false); + + // Click Apply + await driver.find('.test-filter-menu-apply-btn').click(); + + // open Count filter menu + await driver.findContent('.test-filter-field', /Count/).click(); + + // Click all and click Apply + await driver.findContent('.test-filter-menu-bulk-action', /All/).click(); + await driver.find('.test-filter-menu-apply-btn').click(); + + // open Name filter menu + await driver.findContent('.test-filter-field', /Name/).click(); + + // Check Apples and Oranges are unchecked + assert.deepEqual(await driver.findAll('.test-filter-menu-list label', (e) => e.getText()), + ['Apples', 'Bananas', 'Clementines', 'Grapefruit', 'Grapes', 'Oranges']); + assert.equal(await findByName('Apples').find('input').matches(':checked'), false); + assert.equal(await findByName('Oranges').find('input').matches(':checked'), false); + + // click Apply + await driver.find('.test-filter-menu-apply-btn').click(); + + // Open count Filter menu + await driver.findContent('.test-filter-field', /Count/).click(); + + // Check there's only 4 values left + assert.deepEqual(await driver.findAll('.test-filter-menu-list label', (e) => e.getText()), + ['n/a', '-1', '2', '5']); + + // Click Others + await driver.find('.test-filter-menu-summary').click(); + + // click Apply + await driver.find('.test-filter-menu-apply-btn').click(); + + // Open Name filter menu + await driver.findContent('.test-filter-field', /Name/).click(); + + // Check Others is unchecked + assert.equal(await driver.find('.test-filter-menu-summary').find('input').matches(':checked'), false); + assert.equal(await driver.find('.test-filter-menu-summary').find('input').matches(':indeterminate'), false); + + // Click Others + await driver.find('.test-filter-menu-summary').find('input').click(); + await driver.find('.test-filter-menu-apply-btn').click(); + + // Open count filter + await driver.findContent('.test-filter-field', /Count/).click(); + + // Click All and click apply + await driver.findContent('.test-filter-menu-bulk-action', /All/).click(); + await driver.find('.test-filter-menu-apply-btn').click(); + + // open Name filter menu + await driver.findContent('.test-filter-field', /Name/).click(); + + // Check both apples and orages are not checked + assert.equal(await findByName('Apples').find('input').matches(':checked'), false); + assert.equal(await findByName('Oranges').find('input').matches(':checked'), false); + + // Revert to all + await driver.findContent('.test-filter-menu-bulk-action', /All/).click(); + await driver.find('.test-filter-menu-apply-btn').click(); + + // Open Count filter menu and click All + await driver.findContent('.test-filter-field', /Count/).click(); + await driver.findContent('.test-filter-menu-bulk-action', /All/).click(); + await driver.find('.test-filter-menu-apply-btn').click(); + }); + + it('should show count of unique values next to summaries', async () => { + + // add another Apples + await driver.find('.record-add .field').click(); + await driver.sendKeys('Apples', Key.ENTER); + await gu.waitForServer(); + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', 'Count'], rowNums: [1, 2, 3, 4, 5, 6, 7]}), + [ 'Apples', '1', + 'Oranges', '3', + 'Bananas', '2', + 'Grapes', '-1', + 'Grapefruit', 'n/a', + 'Clementines', '5', + 'Apples', '0' + ]); + + // open the Count filter + await driver.findContent('.test-filter-field', /Count/).click(); + + // uncheck 0 and 1 + await findByName(/^0/).click(); + await findByName(/^1/).click(); + + // Click Apply + await driver.find('.test-filter-menu-apply-btn').click(); + + // open the Name filter + await driver.findContent('.test-filter-field', /Name/).click(); + + // check Apples is missing + assert.deepEqual(await driver.findAll('.test-filter-menu-list label', (e) => e.getText()), + ['Bananas', 'Clementines', 'Grapefruit', 'Grapes', 'Oranges']); + + // check count is (1) + assert.deepEqual( + await driver.findAll('.test-filter-menu-summary', (e) => e.find('label').getText()), + ['Others (1)'] + ); + + // close filter + await driver.sendKeys(Key.ESCAPE); + }); + + it('should show a working range filter for numeric columns', async function() { + + // open the Count filter + await driver.findContent('.test-filter-field', /Count/).click(); + + // set min to '2' + await gu.setRangeFilterBound('min', '2'); + await driver.find('.test-filter-menu-apply-btn').click(); + + // check values + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', 'Count'], rowNums: [1, 2, 3, 4]}), + [ 'Oranges', '3', + 'Bananas', '2', + 'Clementines', '5', + '', '' + ] + ); + + // reopen the filter + await driver.findContent('.test-filter-field', /Count/).click(); + + // set max to '4' + await gu.setRangeFilterBound('max', '4'); + await driver.find('.test-filter-menu-apply-btn').click(); + + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', 'Count'], rowNums: [1, 2, 3, 4]}), + [ 'Oranges', '3', + 'Bananas', '2', + '', '', + undefined, undefined + ] + ); + + // remove both min and max + await driver.findContent('.test-filter-field', /Count/).click(); + await gu.setRangeFilterBound('min', null); + await gu.setRangeFilterBound('max', null); + await driver.find('.test-filter-menu-apply-btn').click(); + + // check all values are there + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', 'Count'], rowNums: [1, 2, 3, 4, 5, 6, 7]}), + [ 'Apples', '1', + 'Oranges', '3', + 'Bananas', '2', + 'Grapes', '-1', + 'Grapefruit', 'n/a', + 'Clementines', '5', + 'Apples', '0' + ]); + + }); + + it('should remove new filters when Cancel is clicked in a new filter', async function() { + // Create a new Date filter. + await gu.openColumnMenu('Date', 'Filter'); + assert.deepEqual( + [ + {checked: true, value: 'n/a', count: 1}, + {checked: true, value: '', count: 2}, + {checked: true, value: '2019-07-15', count: 1}, + {checked: true, value: '2019-07-16', count: 1}, + {checked: true, value: '2019-07-17', count: 1}, + {checked: true, value: '2019-07-18', count: 1} + ], + await gu.getFilterMenuState() + ); + + // Check that the Date filter is pinned. + assert.deepEqual( + [ + {name: 'Name', hasUnsavedChanges: true}, + {name: 'Count', hasUnsavedChanges: true}, + {name: 'Date', hasUnsavedChanges: true}, + ], + await gu.getPinnedFilters() + ); + + // Set a min filter of '2019-07-16'. + await gu.setRangeFilterBound('min', '2019-07-16'); + + // Click Cancel, and check that the filter is no longer applied to the table data. + await gu.waitToPass(async () => { + await driver.find('.test-filter-menu-cancel-btn').click(); + assert.isFalse(await driver.find('.test-filter-menu-wrapper').isPresent()); + }); + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', 'Count'], rowNums: [1, 2, 3, 4, 5, 6, 7]}), + [ 'Apples', '1', + 'Oranges', '3', + 'Bananas', '2', + 'Grapes', '-1', + 'Grapefruit', 'n/a', + 'Clementines', '5', + 'Apples', '0' + ] + ); + + // Check that the Date filter was removed. + await gu.openSectionMenu('sortAndFilter'); + assert.isFalse(await driver.findContent('.test-filter-config-filter', /Date/).isPresent()); + await gu.sendKeys(Key.ESCAPE); + assert.deepEqual( + [ + {name: 'Name', hasUnsavedChanges: true}, + {name: 'Count', hasUnsavedChanges: true}, + ], + await gu.getPinnedFilters() + ); + }); + + it('should revert to open state when Cancel is clicked in an existing filter', async function() { + // Open the Count filter. + await driver.findContent('.test-filter-field', /Count/).click(); + + // Filter out 1 and 2. + await driver.findContent('.test-filter-menu-list label', /1/).click(); + await driver.findContent('.test-filter-menu-list label', /2/).click(); + + // Unpin the filter. + await driver.find('.test-filter-menu-pin-btn').click(); + + // Click Cancel, and check that the filter is no longer applied to the table data. + await driver.find('.test-filter-menu-cancel-btn').click(); + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', 'Count'], rowNums: [1, 2, 3, 4, 5, 6, 7]}), + [ 'Apples', '1', + 'Oranges', '3', + 'Bananas', '2', + 'Grapes', '-1', + 'Grapefruit', 'n/a', + 'Clementines', '5', + 'Apples', '0' + ] + ); + + // Check that Count is still pinned to the filter bar. + assert.deepEqual( + [ + {name: 'Name', hasUnsavedChanges: true}, + {name: 'Count', hasUnsavedChanges: true}, + ], + await gu.getPinnedFilters() + ); + + // Check the filter menu state of Count. + await driver.findContent('.test-filter-field', /Count/).click(); + assert.deepEqual( + [ + {checked: true, value: 'n/a', count: 1}, + {checked: true, value: '-1', count: 1}, + {checked: true, value: '0', count: 1}, + {checked: true, value: '1', count: 1}, + {checked: true, value: '2', count: 1}, + {checked: true, value: '3', count: 1}, + {checked: true, value: '5', count: 1}, + ], + await gu.getFilterMenuState() + ); + + await gu.sendKeys(Key.ESCAPE); + }); + + async function testDateLikeColumn(colId: 'Date'|'DateTime') { + + const timeChunk = colId === 'DateTime' ? ' 12:00am' : ''; + const colRegex = new RegExp(colId + '\\b'); + + // add Date Filter + await driver.find('.test-add-filter-btn').click(); + await driver.findContent('.grist-floating-menu li', colRegex).click(); + + // set min to '2019-07-16' + await gu.setRangeFilterBound('min', '2019-07-16'); + await driver.find('.test-filter-menu-apply-btn').click(); + await gu.waitAppFocus(true); + + // check values + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', colId], rowNums: [1, 2, 3, 4]}), + [ 'Apples', '2019-07-17' + timeChunk, + 'Oranges', '2019-07-16' + timeChunk, + 'Bananas', '2019-07-18' + timeChunk, + '', '' + ] + ); + + // reopen the filter + await driver.findContent('.test-filter-field', colRegex).click(); + + // set max to '2019-07-17' + await gu.setRangeFilterBound('max', '2019-07-17'); + await driver.find('.test-filter-menu-apply-btn').click(); + await gu.waitAppFocus(true); + + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', colId], rowNums: [1, 2, 3, 4]}), + [ 'Apples', '2019-07-17' + timeChunk, + 'Oranges', '2019-07-16' + timeChunk, + '', '', + undefined, undefined + ] + ); + + // remove both min and max + await driver.findContent('.test-filter-field', colRegex).click(); + await gu.setRangeFilterBound('min', null); + await gu.setRangeFilterBound('max', null); + await driver.find('.test-filter-menu-apply-btn').click(); + await gu.waitAppFocus(true); + + // check all values are there + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Name', colId], rowNums: [1, 2, 3, 4, 5, 6, 7]}), + [ 'Apples', '2019-07-17' + timeChunk, + 'Oranges', '2019-07-16' + timeChunk, + 'Bananas', '2019-07-18' + timeChunk, + 'Grapes', '', + 'Grapefruit', '2019-07-15' + timeChunk, + 'Clementines', 'n/a', + 'Apples', '', + ]); + } + + + it('should show a working range filter for Date column', async function() { + await testDateLikeColumn('Date'); + }); + + it('should show a working range filter for DateTime column', async function() { + + // adds a DateTime column + await api.applyUserActions(doc.id, [ + ['AddVisibleColumn', 'Table1', 'DateTime', { + type: "DateTime:UTC", widgetOptions: '{"dateFormat": "YYYY-MM-DD", "timeFormat": "h:mma"}' + }], + ['BulkUpdateRecord', 'Table1', [1, 2, 3, 4, 5, 6], { + DateTime: [ + // TODO: fix timezone + "2019-07-17T00:00Z", + "2019-07-16T00:00Z", + "2019-07-18T00:00Z", + "", + "2019-07-15T00:00Z", + "n/a", + ] + }], + ]); + + await testDateLikeColumn('DateTime'); + }); + + it('should have working date range filter also when column is hidden', async function() { + + // hide Date column + await gu.toggleSidePanel('right', 'open'); + await driver.find('.test-right-tab-pagewidget').click(); + await gu.moveToHidden('Date'); + + // add Date filter + await driver.findContent('.test-filter-field', 'Date').click(); + + // start typing date in min bounds and send TAB + await driver.find('.test-filter-menu-min').click(); + await gu.sendKeys('2019-07-14', Key.TAB); + + // check min is set to a valid date + assert.equal(await driver.find('.test-filter-menu-min input').value(), '2019-07-14'); + }); + +}); diff --git a/test/nbrowser/ColumnFilterMenu2.ts b/test/nbrowser/ColumnFilterMenu2.ts new file mode 100644 index 00000000..40a1c069 --- /dev/null +++ b/test/nbrowser/ColumnFilterMenu2.ts @@ -0,0 +1,120 @@ +import * as gu from 'test/nbrowser/gristUtils'; +import { setupTestSuite } from "test/nbrowser/testUtils"; +import { assert, driver } from 'mocha-webdriver'; + + +function getItems() { + return driver.findAll('.test-filter-menu-list label', async (e) => ({ + checked: await e.find('input').isSelected(), + label: await e.getText(), + count: await e.findClosest('div').find('.test-filter-menu-count').getText() + })); +} + +describe('ColumnFilterMenu2', function() { + + this.timeout(20000); + const cleanup = setupTestSuite(); + let mainSession: gu.Session; + let docId: string; + let api: any; + + before(async function() { + mainSession = await gu.session().teamSite.user('user1').login(); + docId = await mainSession.tempNewDoc(cleanup, 'ColumnFilterMenu2.grist', {load: false}); + api = mainSession.createHomeApi(); + // Prepare a table with some interestingly-formatted columns, and some data. + await api.applyUserActions(docId, [ + ['AddTable', 'Test', []], + ['AddVisibleColumn', 'Test', 'Bool', { + type: 'Bool', widgetOptions: JSON.stringify({widget:"TextBox"}) + }], + ['AddVisibleColumn', 'Test', 'Choice', { + type: 'Choice', widgetOptions: JSON.stringify({choices: ['foo', 'bar']}) + }], + ['AddVisibleColumn', 'Test', 'ChoiceList', { + type: 'ChoiceList', widgetOptions: JSON.stringify({choices: ['foo', 'bar']}) + }], + ['AddRecord', 'Test', null, {Bool: true, Choice: 'foo', ChoiceList: ['L', 'foo']}], + ]); + return docId; + }); + + afterEach(() => gu.checkForErrors()); + + it('should show all options for Bool columns', async () => { + await mainSession.loadDoc(`/doc/${docId}/p/2`); + + await gu.openColumnMenu('Bool', 'Filter'); + assert.deepEqual(await getItems(), [ + {checked: true, label: 'false', count: '0'}, + {checked: true, label: 'true', count: '1'}, + ]); + + // click false + await driver.findContent('.test-filter-menu-list label', 'false').click(); + assert.deepEqual(await getItems(), [ + {checked: false, label: 'false', count: '0'}, + {checked: true, label: 'true', count: '1'}, + ]); + + // add new record with Bool=false + const {retValues} = await api.applyUserActions(docId, [ + ['AddRecord', 'Test', null, {Bool: false}], + ]); + + // check record is not shown on screen + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Bool', 'Choice', 'ChoiceList'], rowNums: [1, 2]}), + ['true', 'foo', 'foo', + '', '', '' + ] as any + ); + + // remove added record + await api.applyUserActions(docId, [ + ['RemoveRecord', 'Test', retValues[0]] + ]); + }); + + it('should show all options for Choice/ChoiceList columns', async () => { + await gu.openColumnMenu('Choice', 'Filter'); + assert.deepEqual(await getItems(), [ + {checked: true, label: 'bar', count: '0'}, + {checked: true, label: 'foo', count: '1'}, + ]); + + // click bar + await driver.findContent('.test-filter-menu-list label', 'bar').click(); + assert.deepEqual(await getItems(), [ + {checked: false, label: 'bar', count: '0'}, + {checked: true, label: 'foo', count: '1'}, + ]); + + // add new record with Choice=bar + const {retValues} = await api.applyUserActions(docId, [ + ['AddRecord', 'Test', null, {Choice: 'bar'}], + ]); + + // check record is not shown on screen + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['Bool', 'Choice', 'ChoiceList'], rowNums: [1, 2]}), + ['true', 'foo', 'foo', + '', '', '' + ] as any + ); + + // remove added record + await api.applyUserActions(docId, [ + ['RemoveRecord', 'Test', retValues[0]] + ]); + + // check ChoiceList filter offeres all options + await gu.openColumnMenu('ChoiceList', 'Filter'); + assert.deepEqual(await getItems(), [ + {checked: true, label: 'bar', count: '0'}, + {checked: true, label: 'foo', count: '1'}, + ]); + }); + +}); diff --git a/test/nbrowser/ColumnFilterMenu3.ts b/test/nbrowser/ColumnFilterMenu3.ts new file mode 100644 index 00000000..3c231689 --- /dev/null +++ b/test/nbrowser/ColumnFilterMenu3.ts @@ -0,0 +1,211 @@ +import { assert, driver, Key } from "mocha-webdriver"; +import * as gu from 'test/nbrowser/gristUtils'; +import { setupTestSuite } from "test/nbrowser/testUtils"; + +void(driver); +void(Key); + +async function getValues() { + return driver.findAll('.test-filter-menu-list label', e => e.getText()); +} + +describe('ColumnFilterMenu3', function() { + this.timeout(30000); + const cleanup = setupTestSuite(); + let mainSession: gu.Session; + let docId: string; + before(async () => { + mainSession = await gu.session().teamSite.user('user1').login(); + docId = await mainSession.tempNewDoc(cleanup, 'Search3.grist', {load: false}); + const api = mainSession.createHomeApi(); + // Prepare a table with some interestingly-formatted columns, and some data. + const {retValues} = await api.applyUserActions(docId, [ + ['AddTable', 'Test', []], + ['AddVisibleColumn', 'Test', 'Date', {type: 'Date', widgetOptions: '{"dateFormat":"DD-MM-YYYY"}'}], + ['AddVisibleColumn', 'Test', 'Numeric', {type: 'Numeric'}], + ['AddVisibleColumn', 'Test', 'Int', {type: 'Int'}], + ['AddVisibleColumn', 'Test', 'Ref', {type: 'Ref:Test'}], + ['AddVisibleColumn', 'Test', 'RefList', {type: 'RefList:Test'}], + ]); + await api.applyUserActions(docId, [ + ['UpdateRecord', '_grist_Tables_column', retValues[4].colRef, {visibleCol: retValues[1].colRef}], + ['UpdateRecord', '_grist_Tables_column', retValues[5].colRef, {visibleCol: retValues[1].colRef}], + ['SetDisplayFormula', 'Test', null, retValues[4].colRef, '$Ref.Date'], + ['SetDisplayFormula', 'Test', null, retValues[5].colRef, '$RefList.Date'], + ['AddRecord', 'Test', null, {Date: '22-12-2011', Numeric: 2, Int: 2, Ref: 1, + RefList: ['L', 1, 2]}], + ['AddRecord', 'Test', null, {Date: '20-12-2021', Numeric: 22, Int: 22, Ref: 2, + RefList: ['L', 1]}], + ['AddRecord', 'Test', null, {Date: '20-12-2011', Numeric: 3, Int: 3, Ref: 3, + RefList: ['L', 1, 2, 3]}], + ]); + await mainSession.loadDoc(`/doc/${docId}/p/2`); + }); + + afterEach(async () => { + // close menu if one was opened + if (await driver.find('.grist-floating-menu').isPresent()) { + await driver.sendKeys(Key.ESCAPE); + } + if (await driver.find('.test-filter-menu-wrapper').isPresent()) { + await driver.sendKeys(Key.ESCAPE); + } + }); + + it('should correctly focus between inputs in Numeric columns', async () => { + // A bug was introduced where the search input could no longer be focused if either range + // input had focus. + await gu.openColumnMenu('Numeric', 'Filter'); + + const assertSearchCanBeFocused = async () => { + await driver.find('.test-filter-menu-search-input').click(); + assert.equal( + await driver.switchTo().activeElement().getId(), + await driver.find('.test-filter-menu-search-input').getId() + ); + }; + + await driver.find('.test-filter-menu-min').click(); + await assertSearchCanBeFocused(); + await driver.find('.test-filter-menu-max').click(); + await assertSearchCanBeFocused(); + }); + + it('should have correct order for Numeric column', async () => { + await gu.openColumnMenu('Numeric', 'Filter'); + assert.deepEqual(await getValues(), ['2', '3', '22']); + }); + + it('should have correct order for Integer column', async () => { + await gu.openColumnMenu('Int', 'Filter'); + assert.deepEqual(await getValues(), ['2', '3', '22']); + await driver.find('.test-filter-menu-apply-btn'); + }); + + it('should have correct order for Date column', async () => { + await gu.openColumnMenu('Date', 'Filter'); + assert.deepEqual(await getValues(), ['20-12-2011', '22-12-2011', '20-12-2021']); + }); + + describe('Ref', function() { + + it('should have correct order for Numeric column', async () => { + await gu.toggleSidePanel('right', 'open'); + await gu.openColumnMenu('Ref', 'Options'); + await gu.setRefShowColumn('Numeric'); + await gu.openColumnMenu('Ref', 'Filter'); + assert.deepEqual(await getValues(), ['2', '3', '22']); + }); + it('should have correct order for Integer column', async () => { + await gu.setRefShowColumn('Int'); + await gu.openColumnMenu('Ref', 'Filter'); + assert.deepEqual(await getValues(), ['2', '3', '22']); + }); + it('should have correct order for Date column', async () => { + await gu.setRefShowColumn('Date'); + await gu.openColumnMenu('Ref', 'Filter'); + assert.deepEqual(await getValues(), ['20-12-2011', '22-12-2011', '20-12-2021']); + }); + }); + + describe('RefList', function() { + it('should have correct order for Numeric column', async () => { + await gu.openColumnMenu('RefList', 'Options'); + await gu.setRefShowColumn('Numeric'); + await gu.openColumnMenu('RefList', 'Filter'); + assert.deepEqual(await getValues(), ['2', '3', '22']); + }); + it('should have correct order for Integer column', async () => { + await gu.setRefShowColumn('Int'); + await gu.openColumnMenu('RefList', 'Filter'); + assert.deepEqual(await getValues(), ['2', '3', '22']); + }); + it('should have correct order for Date column', async () => { + await gu.setRefShowColumn('Date'); + await gu.openColumnMenu('RefList', 'Filter'); + assert.deepEqual(await getValues(), ['20-12-2011', '22-12-2011', '20-12-2021']); + }); + }); + + describe('id mismatch', function() { + // This test intent to replicate a bug that happened with filters. For the bug to happen we need + // to have a view field row id (here view field of col B) that matches the row id of another + // column (here col A). When this happen, and when col A is hidden, and when users open the + // column menu for B, the filter apply mistakingly to column A values as well, which could + // intail unexpected result depending on the values of A. + + let docId2: string; + before(async () => { + docId2 = await mainSession.tempNewDoc(cleanup, 'ColumnFilterMenu3IdMismatch.grist', {load: false}); + const api = mainSession.createHomeApi(); + await api.applyUserActions(docId2, [ + ['BulkAddRecord', 'Table1', [null, null, null], {A: [1, 3, 3], B: [1, 1, 3]}], + ['RemoveRecord', "_grist_Views_section_field", 1], // Hide 'A' column + ]); + }); + it('filters should work correctly', async function() { + await mainSession.loadDoc(`/doc/${docId2}/p/1`); + + // filter B by {max: 2} + await gu.openColumnMenu('B', 'Filter'); + await gu.setRangeFilterBound('max', '2'); + await driver.find('.test-filter-menu-apply-btn').click(); + + // check filter does not behaves in-correctly (here mostly to show what the problem looked + // like) + assert.notDeepEqual( + await gu.getVisibleGridCells({cols: ['B'], rowNums: [1, 2, 3]}), + [ '1', '', undefined] + ); + + // check filter does behave correctly + assert.deepEqual( + await gu.getVisibleGridCells({cols: ['B'], rowNums: [1, 2, 3]}), + [ '1', '1', ''] + ); + }); + }); + + describe('empty choice columns', function() { + // Previously, a bug would cause an error to be thrown when filtering an empty + // choice or choice list column. This suite replicates that scenario. + + async function assertEmptyRowCount(count: number) { + assert.deepEqual( + await driver.findAll('.test-filter-menu-list label', (e) => e.getText()), + [''] + ); + assert.deepEqual( + await driver.findAll('.test-filter-menu-list .test-filter-menu-count', (e) => e.getText()), + [count.toString()], + ); + } + + async function assertEmptyColumnIsFilterable( + columnType: 'Choice' | 'Choice List' | 'Reference List' + ) { + const columnLabel = `Empty ${columnType}`; + await gu.addColumn(columnLabel); + await gu.setType(new RegExp(`${columnType}$`)); + await gu.openColumnMenu(columnLabel, 'Filter'); + await assertEmptyRowCount(2); + await gu.sendKeys(Key.ESCAPE); + } + + afterEach(() => gu.checkForErrors()); + + it('should not throw an error when filtering empty choice columns', async function() { + await assertEmptyColumnIsFilterable('Choice'); + }); + + it('should not throw an error when filtering empty choice list columns', async function() { + await assertEmptyColumnIsFilterable('Choice List'); + }); + + it('should not throw an error when filtering empty reference list columns', async function() { + // Note: this wasn't impacted by the aforementioned bug; this test is only included for + // completeness. + await assertEmptyColumnIsFilterable('Reference List'); + }); + }); +}); diff --git a/test/nbrowser/SectionFilter.ts b/test/nbrowser/SectionFilter.ts new file mode 100644 index 00000000..ab4caf17 --- /dev/null +++ b/test/nbrowser/SectionFilter.ts @@ -0,0 +1,531 @@ +import {assert, driver, Key, until} from 'mocha-webdriver'; +import * as gu from 'test/nbrowser/gristUtils'; +import {setupTestSuite} from 'test/nbrowser/testUtils'; + +describe('SectionFilter', function() { + this.timeout(60000); + const cleanup = setupTestSuite(); + + describe('Core tests', function() { + + before(async function() { + this.timeout(10000); + const session = await gu.session().teamSite.login(); + await session.tempNewDoc(cleanup); + }); + + it('should be able to open / close filter menu', async () => { + const menu = await gu.openColumnMenu('A', 'Filter'); + assert.equal(await menu.find('.test-filter-menu-list').getText(), 'No matching values'); + await driver.sendKeys(Key.ESCAPE); + await driver.wait(until.stalenessOf(menu)); + }); + + it('should filter out records in response to filter menu selections', async () => { + this.timeout(10000); + + await gu.enterGridRows({col: 'A', rowNum: 1}, [ + ['Apples', '1'], + ['Oranges', '2'], + ['Bananas', '1'], + ['Apples', '2'], + ['Bananas', '1'], + ['Apples', '2'], + ]); + + const menu = await gu.openColumnMenu('A', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: 'Apples', count: 3}, + { checked: true, value: 'Bananas', count: 2}, + { checked: true, value: 'Oranges', count: 1} + ]); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6]), + ['Apples', 'Oranges', 'Bananas', 'Apples', 'Bananas', 'Apples']); + + await menu.findContent('label', /Apples/).click(); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: false, value: 'Apples', count: 3}, + { checked: true, value: 'Bananas', count: 2}, + { checked: true, value: 'Oranges', count: 1} + ]); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3]), + ['Oranges', 'Bananas', 'Bananas']); + + await menu.findContent('label', /Apples/).click(); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: 'Apples', count: 3}, + { checked: true, value: 'Bananas', count: 2}, + { checked: true, value: 'Oranges', count: 1} + ]); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6]), + ['Apples', 'Oranges', 'Bananas', 'Apples', 'Bananas', 'Apples']); + + await driver.sendKeys(Key.ESCAPE); + }); + + it('should undo filter changes on cancel', async () => { + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6]), + ['Apples', 'Oranges', 'Bananas', 'Apples', 'Bananas', 'Apples']); + + const menu = await gu.openColumnMenu('A', 'Filter'); + + await menu.findContent('label', /Apples/).click(); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: false, value: 'Apples', count: 3}, + { checked: true, value: 'Bananas', count: 2}, + { checked: true, value: 'Oranges', count: 1} + ]); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3]), + ['Oranges', 'Bananas', 'Bananas']); + + await menu.find('.test-filter-menu-cancel-btn').click(); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6]), + ['Apples', 'Oranges', 'Bananas', 'Apples', 'Bananas', 'Apples']); + }); + + it('should display new/updated rows even when only certain values are filtered in', async () => { + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6]), + ['Apples', 'Oranges', 'Bananas', 'Apples', 'Bananas', 'Apples']); + + let menu = await gu.openColumnMenu('A', 'Filter'); + + // Put the filter into the "inclusion" state, with nothing selected initially. + assert.deepEqual( + await driver.findAll('.test-filter-menu-bulk-action:not(:disabled)', (e) => e.getText()), + ['None']); + await driver.findContent('.test-filter-menu-bulk-action', /None/).click(); + assert.deepEqual( + await driver.findAll('.test-filter-menu-bulk-action:not(:disabled)', (e) => e.getText()), + ['All']); + + // Include only "Apples". + await menu.findContent('label', /Apples/).click(); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: 'Apples', count: 3}, + { checked: false, value: 'Bananas', count: 2}, + { checked: false, value: 'Oranges', count: 1} + ]); + + await driver.find('.test-filter-menu-apply-btn').click(); + + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4]), + ['Apples', 'Apples', 'Apples', '']); + + // Update first row to Oranges; it should remain shown. + await gu.getCell(0, 1).click(); + await gu.enterCell('Oranges'); + + // Enter a new row using a keyboard shortcut. + await driver.find('body').sendKeys(Key.chord(await gu.modKey(), Key.ENTER)); + + // Enter a new row by typing in a value into the "add-row". + await driver.find('.gridview_row .record-add .field').click(); + await gu.enterCell('Bananas'); + + // Ensure all 3 changes are visible. + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6]), + ['Oranges', 'Apples', '', 'Apples', 'Bananas', '']); + + // Check that the filter menu looks as expected. + menu = await gu.openColumnMenu('A', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: false, value: '', count: 1}, + { checked: true, value: 'Apples', count: 2}, + { checked: false, value: 'Bananas', count: 3}, + { checked: false, value: 'Oranges', count: 2} + ]); + + // Apply the filter to make it only-Apples again. + await menu.find('.test-filter-menu-apply-btn').click(); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3]), + ['Apples', 'Apples', '']); + + // Reset the filter + menu = await gu.openColumnMenu('A', 'Filter'); + assert.deepEqual( + await driver.findAll('.test-filter-menu-bulk-action:not([class*=-disabled])', (e) => e.getText()), + ['All', 'None']); + await driver.findContent('.test-filter-menu-bulk-action', /All/).click(); + await menu.find('.test-filter-menu-apply-btn').click(); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6, 7, 8]), + ['Oranges', 'Oranges', 'Bananas', 'Apples', 'Bananas', '', 'Apples', 'Bananas']); + + // Restore changes of this test case. + await gu.undo(3); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6]), + ['Apples', 'Oranges', 'Bananas', 'Apples', 'Bananas', 'Apples']); + }); + + it('should display new/updated rows even when filtered, but refilter on menu changes', async () => { + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6]), + ['Apples', 'Oranges', 'Bananas', 'Apples', 'Bananas', 'Apples']); + + let menu = await gu.openColumnMenu('A', 'Filter'); + + await menu.findContent('label', /Apples/).click(); + await driver.find('.test-filter-menu-apply-btn').click(); + + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3]), + ['Oranges', 'Bananas', 'Bananas']); + + // Update Oranges to Apples and make sure it's not filtered out + await (await gu.getCell(0, 1)).click(); + await gu.enterCell('Apples'); + + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3]), + ['Apples', 'Bananas', 'Bananas']); + + // Set back to Oranges and make sure it stays + await driver.sendKeys(Key.UP); + await gu.enterCell('Oranges'); + + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3]), + ['Oranges', 'Bananas', 'Bananas']); + + // Enter two new rows and make sure they're also not filtered out + await driver.find('.gridview_row .record-add .field').click(); + await gu.enterCell('Apples'); + await gu.enterCell('Bananas'); + + // Enter a new row using a keyboard shortcut. + await driver.find('body').sendKeys(Key.chord(await gu.modKey(), Key.ENTER)); + await gu.waitForServer(); + + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6]), + ['Oranges', 'Bananas', 'Bananas', 'Apples', 'Bananas', '']); + + menu = await gu.openColumnMenu('A', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 1}, + { checked: false, value: 'Apples', count: 4}, + { checked: true, value: 'Bananas', count: 3}, + { checked: true, value: 'Oranges', count: 1} + ]); + + await menu.findContent('label', /Apples/).click(); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6, 7, 8]), + ['Apples', 'Oranges', 'Bananas', 'Apples', 'Bananas', 'Apples', 'Apples', 'Bananas']); + await menu.findContent('label', /Apples/).click(); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4]), + ['Oranges', 'Bananas', 'Bananas', 'Bananas']); + await driver.sendKeys(Key.ESCAPE); + }); + }); + + describe('Type tests', function() { + + before(async function() { + const session = await gu.session().teamSite.login(); + await session.tempDoc(cleanup, 'FilterTest.grist'); + }); + + it('should properly filter strings', async () => { + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6, 7, 8]), + ['Foo', 'Bar', '1', '2.0', '2016-01-01', '5+6', '', '']); + + const menu = await gu.openColumnMenu('Text', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 1}, + { checked: true, value: '1', count: 1}, + { checked: true, value: '2.0', count: 1}, + { checked: true, value: '5+6', count: 1}, + { checked: true, value: '2016-01-01', count: 1}, + { checked: true, value: 'Bar', count: 1}, + { checked: true, value: 'Foo', count: 1} + ]); + await menu.findContent('label', /^$/).click(); + await menu.findContent('label', /Bar/).click(); + assert.deepEqual(await gu.getVisibleGridCells(0, [1, 2, 3, 4, 5, 6, 7]), + ['Foo', '1', '2.0', '2016-01-01', '5+6', '', undefined]); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + + it('should properly filter numbers', async () => { + assert.deepEqual(await gu.getVisibleGridCells(1, [1, 2, 3, 4, 5, 6, 7, 8]), + ['5.00', '6.00', '7.00', '-1.00', 'foo', '0.00', '', '']); + + const menu = await gu.openColumnMenu('Number', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 1}, + { checked: true, value: 'foo', count: 1}, + { checked: true, value: '-1.00', count: 1}, + { checked: true, value: '0.00', count: 1}, + { checked: true, value: '5.00', count: 1}, + { checked: true, value: '6.00', count: 1}, + { checked: true, value: '7.00', count: 1}, + ]); + await menu.findContent('label', /^$/).click(); + await menu.findContent('label', /7/).click(); + await menu.findContent('label', /foo/).click(); + assert.deepEqual(await gu.getVisibleGridCells(1, [1, 2, 3, 4, 5, 6]), + ['5.00', '6.00', '-1.00', '0.00', '', undefined]); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + it('should properly filter dates', async () => { + assert.deepEqual(await gu.getVisibleGridCells(2, [1, 2, 3, 4, 5, 6, 7, 8]), + ['2019-06-03', '2019-06-07', '2019-06-05', 'bar', '2019-06-123', '0', '', '']); + + const menu = await gu.openColumnMenu('Date', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 1}, + { checked: true, value: '2019-06-123', count: 1}, + { checked: true, value: 'bar', count: 1}, + { checked: true, value: '0', count: 1}, + { checked: true, value: '2019-06-03', count: 1}, + { checked: true, value: '2019-06-05', count: 1}, + { checked: true, value: '2019-06-07', count: 1}, + ]); + await menu.findContent('label', /^$/).click(); + await menu.findContent('label', /2019-06-05/).click(); + await menu.findContent('label', /bar/).click(); + assert.deepEqual(await gu.getVisibleGridCells(2, [1, 2, 3, 4, 5, 6]), + ['2019-06-03', '2019-06-07', '2019-06-123', '0', '', undefined]); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + it('should properly search through list of date to filter', async () => { + const menu = await gu.openColumnMenu('Date', 'Filter'); + assert.lengthOf(await gu.getFilterMenuState(), 7); + await driver.sendKeys('07'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '2019-06-07', count: 1} + ]); + assert.deepEqual( + await menu.findAll('.test-filter-menu-list label', (e) => e.getText()), + ['2019-06-07'] + ); + await menu.findContent('.test-filter-menu-bulk-action', /All Shown/).click(); + assert.deepEqual( + await gu.getVisibleGridCells(2, [1, 2]), + ['2019-06-07', ''] + ); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + it('should properly filter formulas', async () => { + assert.deepEqual(await gu.getVisibleGridCells(3, [1, 2, 3, 4, 5, 6, 7, 8]), + ['25', '36', '49', '1', '#TypeError', '0', '#TypeError', '']); + + const menu = await gu.openColumnMenu('Formula', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '#TypeError', count: 2}, + { checked: true, value: '0', count: 1}, + { checked: true, value: '1', count: 1}, + { checked: true, value: '25', count: 1}, + { checked: true, value: '36', count: 1}, + { checked: true, value: '49', count: 1}, + ]); + + await menu.findContent('label', /0/).click(); + await menu.findContent('label', /#TypeError/).click(); + await menu.findContent('label', /25/).click(); + + assert.deepEqual(await gu.getVisibleGridCells(3, [1, 2, 3, 4, 5]), + ['36', '49', '1', '', undefined]); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + it('should properly filter references', async () => { + assert.deepEqual(await gu.getVisibleGridCells(4, [1, 2, 3, 4, 5, 6, 7, 8]), + ['alice', 'carol', 'bob', 'denis', '0', 'denis', '', '']); + + const menu = await gu.openColumnMenu('Reference', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 1}, + { checked: true, value: '#Invalid Ref: 0', count: 1}, + { checked: true, value: '#Invalid Ref: denis', count: 2}, + { checked: true, value: 'alice', count: 1}, + { checked: true, value: 'bob', count: 1}, + { checked: true, value: 'carol', count: 1}, + ]); + + await menu.findContent('label', /^$/).click(); + await menu.findContent('label', /#Invalid Ref: denis/).click(); + await menu.findContent('label', /bob/).click(); + + assert.deepEqual(await gu.getVisibleGridCells(4, [1, 2, 3, 4, 5]), + ['alice', 'carol', '0', '', undefined]); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + it('should properly filter choice lists', async () => { + assert.deepEqual(await gu.getVisibleGridCells(5, [1, 2, 3, 4, 5, 6, 7, 8]), + ['Foo\nBar\nBaz', 'Foo\nBar', 'Foo', 'InvalidChoice', 'Baz\nBaz\nBaz', 'Bar\nBaz', '', '']); + + const menu = await gu.openColumnMenu('ChoiceList', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 1}, + { checked: true, value: 'Bar', count: 3}, + { checked: true, value: 'Baz', count: 5}, + { checked: true, value: 'Foo', count: 3}, + { checked: true, value: 'InvalidChoice', count: 1}, + ]); + + // Check that all the choices are rendered in the right colors. + const choiceColors = await menu.findAll( + 'label .test-filter-menu-choice-token', + async (c) => [await c.getCssValue('background-color'), await c.getCssValue('color')] + ); + + assert.deepEqual( + choiceColors, + [ + [ 'rgba(254, 204, 129, 1)', 'rgba(0, 0, 0, 1)' ], + [ 'rgba(53, 253, 49, 1)', 'rgba(0, 0, 0, 1)' ], + [ 'rgba(204, 254, 254, 1)', 'rgba(0, 0, 0, 1)' ], + [ 'rgba(255, 255, 255, 1)', 'rgba(0, 0, 0, 1)' ] + ] + ); + + // Check that Foo is rendered with font options. + const boldFonts = await menu.findAll( + 'label .test-filter-menu-choice-token.font-italic.font-bold', + (c) => c.getText() + ); + + assert.deepEqual(boldFonts, ['Foo']); + + await menu.findContent('label', /^$/).click(); + await menu.findContent('label', /Bar/).click(); + await menu.findContent('label', /Baz/).click(); + + assert.deepEqual(await gu.getVisibleGridCells(5, [1, 2, 3, 4, 5]), + ['Foo\nBar\nBaz', 'Foo\nBar', 'Foo', 'InvalidChoice', '']); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + it('should properly filter errors in choice lists', async () => { + assert.deepEqual(await gu.getVisibleGridCells(6, [1, 2, 3, 4, 5, 6, 7, 8]), + ['25.0', '36.0', '49.0', '1.0', '#TypeError', '', '#TypeError', '']); + + await gu.scrollIntoView(gu.getColumnHeader('ChoiceListErrors')); + const menu = await gu.openColumnMenu('ChoiceListErrors', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 1}, + { checked: true, value: '#TypeError', count: 2}, + { checked: true, value: '1.0', count: 1}, + { checked: true, value: '25.0', count: 1}, + { checked: true, value: '36.0', count: 1}, + { checked: true, value: '49.0', count: 1}, + { checked: true, value: 'A', count: 0}, + { checked: true, value: 'B', count: 0}, + { checked: true, value: 'C', count: 0}, + { checked: true, value: 'D', count: 0}, + ]); + + await menu.findContent('label', /^$/).click(); + await menu.findContent('label', /#TypeError/).click(); + await menu.findContent('label', /25\.0/).click(); + await menu.findContent('label', /36\.0/).click(); + await menu.findContent('label', /49\.0/).click(); + + assert.deepEqual(await gu.getVisibleGridCells(6, [1, 2]), + ['1.0', '']); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + it('should properly filter choices', async () => { + assert.deepEqual(await gu.getVisibleGridCells(7, [1, 2, 3, 4, 5, 6, 7, 8]), + ['Red', 'Orange', 'Yellow', 'InvalidChoice', '', 'Red', '', '']); + + const menu = await gu.openColumnMenu('Choice', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 2}, + { checked: true, value: 'InvalidChoice', count: 1}, + { checked: true, value: 'Orange', count: 1}, + { checked: true, value: 'Red', count: 2}, + { checked: true, value: 'Yellow', count: 1}, + ]); + + // Check that all the choices are rendered in the right colors. + const choiceColors = await menu.findAll( + 'label .test-filter-menu-choice-token', + async (c) => [await c.getCssValue('background-color'), await c.getCssValue('color')] + ); + + assert.deepEqual( + choiceColors, + [ + [ 'rgba(255, 255, 255, 1)', 'rgba(0, 0, 0, 1)' ], + [ 'rgba(254, 204, 129, 1)', 'rgba(0, 0, 0, 1)' ], + [ 'rgba(252, 54, 59, 1)', 'rgba(255, 255, 255, 1)' ], + [ 'rgba(255, 250, 205, 1)', 'rgba(0, 0, 0, 1)' ] + ] + ); + + // Check that Red is rendered with font options. + const withFonts = await menu.findAll( + 'label .test-filter-menu-choice-token.font-underline.font-strikethrough', + (c) => c.getText() + ); + + assert.deepEqual(withFonts, ['Red']); + + await menu.findContent('label', /InvalidChoice/).click(); + await menu.findContent('label', /Orange/).click(); + await menu.findContent('label', /Yellow/).click(); + + assert.deepEqual(await gu.getVisibleGridCells(7, [1, 2, 3, 4, 5]), + ['Red', '', 'Red', '', '']); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + it('should properly filter reference lists', async () => { + assert.deepEqual(await gu.getVisibleGridCells(8, [1, 2, 3, 4, 5, 6, 7, 8]), + ['alice\ncarol', 'bob', 'carol\nbob\nalice', '[u\'denis\']', '[u\'0\']', '[u\'denis\', u\'edward\']', '', '']); + + const menu = await gu.openColumnMenu('ReferenceList', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 1 }, + { checked: true, value: '#Invalid RefList: [u\'0\']', count: 1 }, + { + checked: true, + value: '#Invalid RefList: [u\'denis\', u\'edward\']', + count: 1 + }, + { + checked: true, + value: '#Invalid RefList: [u\'denis\']', + count: 1 + }, + { checked: true, value: 'alice', count: 2 }, + { checked: true, value: 'bob', count: 2 }, + { checked: true, value: 'carol', count: 2 } + ]); + + await menu.findContent('label', /^$/).click(); + await menu.findContent('label', /bob/).click(); + await menu.findContent('label', /#Invalid RefList: \[u'0'\]/).click(); + + assert.deepEqual(await gu.getVisibleGridCells(8, [1, 2, 3, 4, 5]), + ['alice\ncarol', 'carol\nbob\nalice', '[u\'denis\']', '[u\'denis\', u\'edward\']', '']); + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + + it('should reflect the section show column setting in the filter menu', async () => { + // Scroll col 3 into view to make sure col 4 is clickable + await gu.scrollIntoView(gu.getCell(3, 1)); + + // Change the show column setting of the Reference column to 'color'. + await gu.getCell(4, 1).click(); + await gu.toggleSidePanel('right', 'open'); + await driver.find('.test-right-tab-field').click(); + await gu.setRefShowColumn('color'); + + // Open the filter menu for Reference, and check that the values are now from 'color'. + const menu = await gu.openColumnMenu('Reference', 'Filter'); + assert.deepEqual(await gu.getFilterMenuState(), [ + { checked: true, value: '', count: 1 }, + { checked: true, value: '#Invalid Ref: 0', count: 1 }, + { checked: true, value: '#Invalid Ref: denis', count: 2 }, + { checked: true, value: 'blue', count: 1 }, + { checked: true, value: 'green', count: 1 }, + { checked: true, value: 'red', count: 1 } + ]); + + await menu.find('.test-filter-menu-cancel-btn').click(); + }); + }); +});