mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Fix and move filter tests to grist-core
Summary: A few tests that hadn't been ported to grist-core yet began failing after a change in behavior with the column filter menu. Test Plan: Existing tests. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D4260
This commit is contained in:
parent
76a43129f1
commit
15590950e6
621
test/nbrowser/ColumnFilterMenu.ts
Normal file
621
test/nbrowser/ColumnFilterMenu.ts
Normal file
@ -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');
|
||||
});
|
||||
|
||||
});
|
120
test/nbrowser/ColumnFilterMenu2.ts
Normal file
120
test/nbrowser/ColumnFilterMenu2.ts
Normal file
@ -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'},
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
211
test/nbrowser/ColumnFilterMenu3.ts
Normal file
211
test/nbrowser/ColumnFilterMenu3.ts
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
531
test/nbrowser/SectionFilter.ts
Normal file
531
test/nbrowser/SectionFilter.ts
Normal file
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user