2023-02-08 10:07:57 +00:00
|
|
|
import * as _ from 'lodash';
|
2023-07-31 20:10:59 +00:00
|
|
|
import {assert, driver} from 'mocha-webdriver';
|
2023-02-08 10:07:57 +00:00
|
|
|
import {enterRulePart, findDefaultRuleSet} from 'test/nbrowser/aclTestUtils';
|
|
|
|
import * as gu from 'test/nbrowser/gristUtils';
|
2023-07-31 20:10:59 +00:00
|
|
|
import {setupTestSuite} from 'test/nbrowser/testUtils';
|
2023-02-08 10:07:57 +00:00
|
|
|
|
|
|
|
describe('SelectBySummary', function() {
|
|
|
|
this.timeout(50000);
|
2023-07-31 20:10:59 +00:00
|
|
|
const cleanup = setupTestSuite();
|
|
|
|
let headers: Record<string, string>;
|
2023-02-08 10:07:57 +00:00
|
|
|
gu.bigScreen();
|
|
|
|
|
|
|
|
before(async function() {
|
2023-07-31 20:10:59 +00:00
|
|
|
const session = await gu.session().teamSite.login();
|
|
|
|
await session.tempDoc(cleanup, 'SelectBySummary.grist');
|
|
|
|
headers = {
|
|
|
|
Authorization: `Bearer ${session.getApiKey()}`
|
|
|
|
};
|
2023-02-08 10:07:57 +00:00
|
|
|
});
|
|
|
|
|
2023-07-31 20:10:59 +00:00
|
|
|
it('should filter a source table selected by a summary table (first option)', async function() {
|
2023-02-08 10:07:57 +00:00
|
|
|
await checkSelectingRecords(
|
2023-07-31 20:10:59 +00:00
|
|
|
headers,
|
2023-02-08 10:07:57 +00:00
|
|
|
['onetwo'],
|
|
|
|
[
|
|
|
|
'1', '16',
|
|
|
|
'2', '20',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
'1', 'a', '1',
|
|
|
|
'1', 'b', '3',
|
|
|
|
'1', 'a\nb', '5',
|
|
|
|
'1', '', '7',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'2', 'a', '2',
|
|
|
|
'2', 'b', '4',
|
|
|
|
'2', 'a\nb', '6',
|
|
|
|
'2', '', '8',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
);
|
2023-07-31 20:10:59 +00:00
|
|
|
});
|
2023-02-08 10:07:57 +00:00
|
|
|
|
2023-07-31 20:10:59 +00:00
|
|
|
it('should filter a source table selected by a summary table (second option)', async function() {
|
2023-02-08 10:07:57 +00:00
|
|
|
await checkSelectingRecords(
|
2023-07-31 20:10:59 +00:00
|
|
|
headers,
|
2023-02-08 10:07:57 +00:00
|
|
|
['choices'],
|
|
|
|
[
|
|
|
|
'a', '14',
|
|
|
|
'b', '18',
|
|
|
|
'', '15',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
'1', 'a', '1',
|
|
|
|
'2', 'a', '2',
|
|
|
|
'1', 'a\nb', '5',
|
|
|
|
'2', 'a\nb', '6',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'1', 'b', '3',
|
|
|
|
'2', 'b', '4',
|
|
|
|
'1', 'a\nb', '5',
|
|
|
|
'2', 'a\nb', '6',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'1', '', '7',
|
|
|
|
'2', '', '8',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
);
|
2023-07-31 20:10:59 +00:00
|
|
|
});
|
2023-02-08 10:07:57 +00:00
|
|
|
|
2023-07-31 20:10:59 +00:00
|
|
|
it('should filter a source table selected by a summary table (both options)', async function() {
|
2023-02-08 10:07:57 +00:00
|
|
|
await checkSelectingRecords(
|
2023-07-31 20:10:59 +00:00
|
|
|
headers,
|
2023-02-08 10:07:57 +00:00
|
|
|
['onetwo', 'choices'],
|
|
|
|
[
|
|
|
|
'1', 'a', '6',
|
|
|
|
'2', 'a', '8',
|
|
|
|
'1', 'b', '8',
|
|
|
|
'2', 'b', '10',
|
|
|
|
'1', '', '7',
|
|
|
|
'2', '', '8',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
'1', 'a', '1',
|
|
|
|
'1', 'a\nb', '5',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'2', 'a', '2',
|
|
|
|
'2', 'a\nb', '6',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'1', 'b', '3',
|
|
|
|
'1', 'a\nb', '5',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'2', 'b', '4',
|
|
|
|
'2', 'a\nb', '6',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'1', '', '7',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'2', '', '8',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should create new rows in the source table (link target) with correct default values',
|
|
|
|
gu.revertChanges(async function() {
|
|
|
|
// Select the record with ['2', 'a'] in the summary table
|
|
|
|
// so those values will be used as defaults in the source table
|
|
|
|
await gu.getCell({section: 'TABLE1 [by onetwo, choices]', col: 'rownum', rowNum: 2}).click();
|
|
|
|
|
2023-12-18 17:50:57 +00:00
|
|
|
// Create new records with rownum = 99 and 100
|
2023-02-08 10:07:57 +00:00
|
|
|
await gu.getCell({section: 'TABLE1', col: 'rownum', rowNum: 3}).click();
|
|
|
|
await gu.enterCell('99');
|
2023-12-18 17:50:57 +00:00
|
|
|
await gu.enterCell('100');
|
2023-02-08 10:07:57 +00:00
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
await gu.getVisibleGridCells({
|
|
|
|
section: 'TABLE1',
|
|
|
|
cols: ['onetwo', 'choices', 'rownum'],
|
2023-12-18 17:50:57 +00:00
|
|
|
rowNums: [1, 2, 3, 4, 5],
|
2023-02-08 10:07:57 +00:00
|
|
|
}),
|
2023-12-18 17:50:57 +00:00
|
|
|
[
|
|
|
|
'2', 'a', '2',
|
|
|
|
'2', 'a\nb', '6',
|
|
|
|
// The two rows we just added.
|
|
|
|
// The filter link sets the default value 'a'.
|
|
|
|
// It can't set a default value for 'onetwo' because that's a formula column.
|
|
|
|
// This first row doesn't match the filter link, but it still shows temporarily.
|
|
|
|
'1', 'a', '99',
|
|
|
|
'2', 'a', '100',
|
|
|
|
'', '', '', // new row
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
// Select a different record in the summary table, sanity check the linked table.
|
|
|
|
await gu.getCell({section: 'TABLE1 [by onetwo, choices]', col: 'rownum', rowNum: 3}).click();
|
|
|
|
assert.deepEqual(
|
|
|
|
await gu.getVisibleGridCells({
|
|
|
|
section: 'TABLE1',
|
|
|
|
cols: ['onetwo', 'choices', 'rownum'],
|
|
|
|
rowNums: [1, 2, 3],
|
|
|
|
}),
|
|
|
|
[
|
|
|
|
'1', 'b', '3',
|
|
|
|
'1', 'a\nb', '5',
|
|
|
|
'', '', '', // new row
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
// Now go back to the previously selected summary table row.
|
|
|
|
await gu.getCell({section: 'TABLE1 [by onetwo, choices]', col: 'rownum', rowNum: 2}).click();
|
|
|
|
assert.deepEqual(
|
|
|
|
await gu.getVisibleGridCells({
|
|
|
|
section: 'TABLE1',
|
|
|
|
cols: ['onetwo', 'choices', 'rownum'],
|
|
|
|
rowNums: [1, 2, 3, 4],
|
|
|
|
}),
|
|
|
|
[
|
|
|
|
'2', 'a', '2',
|
|
|
|
'2', 'a\nb', '6',
|
|
|
|
// The row ['1', 'a', '99'] is now filtered out as normal.
|
|
|
|
'2', 'a', '100',
|
|
|
|
'', '', '', // new row
|
|
|
|
],
|
2023-02-08 10:07:57 +00:00
|
|
|
);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
it('should filter a summary table selected by a less detailed summary table', async function() {
|
|
|
|
// Delete the Table1 widget so that we can hide the table in ACL without hiding the whole page.
|
2024-07-24 11:33:53 +00:00
|
|
|
await gu.deleteWidget('TABLE1');
|
2023-02-08 10:07:57 +00:00
|
|
|
|
|
|
|
// Open the ACL UI
|
|
|
|
await driver.find('.test-tools-access-rules').click();
|
|
|
|
await driver.findWait('.test-rule-set', 2000); // Wait for initialization fetch to complete.
|
|
|
|
|
|
|
|
// Deny all access to Table1.
|
|
|
|
await driver.findContentWait('button', /Add Table Rules/, 2000).click();
|
|
|
|
await driver.findContentWait('.grist-floating-menu li', /Table1/, 3000).click();
|
|
|
|
const ruleSet = findDefaultRuleSet(/Table1/);
|
|
|
|
await enterRulePart(ruleSet, 1, null, 'Deny All');
|
|
|
|
await driver.find('.test-rules-save').click();
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
|
|
|
// Go back to the main page.
|
|
|
|
await gu.getPageItem('Table1').click();
|
|
|
|
|
|
|
|
// Now check filter linking, but with the detailed summary 'TABLE1 [by onetwo, choices]' as the target,
|
|
|
|
// selecting by the two less detailed summaries.
|
|
|
|
// There was a bug previously that this would not work while the summary source table (Table1) was hidden.
|
|
|
|
await checkSelectingRecords(
|
2023-07-31 20:10:59 +00:00
|
|
|
headers,
|
2023-02-08 10:07:57 +00:00
|
|
|
['onetwo'],
|
|
|
|
[
|
|
|
|
'1', '16',
|
|
|
|
'2', '20',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
'1', 'a', '6',
|
|
|
|
'1', 'b', '8',
|
|
|
|
'1', '', '7',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'2', 'a', '8',
|
|
|
|
'2', 'b', '10',
|
|
|
|
'2', '', '8',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
// This argument was not used in the previous test, as TABLE1 is the default.
|
|
|
|
'TABLE1 [by onetwo, choices]',
|
|
|
|
);
|
|
|
|
|
|
|
|
await checkSelectingRecords(
|
2023-07-31 20:10:59 +00:00
|
|
|
headers,
|
2023-02-08 10:07:57 +00:00
|
|
|
['choices'],
|
|
|
|
[
|
|
|
|
'a', '14',
|
|
|
|
'b', '18',
|
|
|
|
'', '15',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
[
|
|
|
|
'1', 'a', '6',
|
|
|
|
'2', 'a', '8',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'1', 'b', '8',
|
|
|
|
'2', 'b', '10',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'1', '', '7',
|
|
|
|
'2', '', '8',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'TABLE1 [by onetwo, choices]',
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Makes `targetSection` select by the existing summary table grouped by groubyColumns.
|
|
|
|
* Asserts that the summary table has the data summaryData under groubyColumns and rownum.
|
|
|
|
* Asserts that clicking each row in the summary table filters the target section
|
|
|
|
* to the corresponding subarray of `targetData`.
|
|
|
|
*/
|
|
|
|
async function checkSelectingRecords(
|
2023-07-31 20:10:59 +00:00
|
|
|
headers: Record<string, string>,
|
2023-02-08 10:07:57 +00:00
|
|
|
groubyColumns: string[],
|
|
|
|
summaryData: string[],
|
|
|
|
targetData: string[][],
|
|
|
|
targetSection: string = 'TABLE1',
|
|
|
|
) {
|
|
|
|
const summarySection = `TABLE1 [by ${groubyColumns.join(', ')}]`;
|
|
|
|
|
2023-06-29 07:15:14 +00:00
|
|
|
await gu.openSelectByForSection(targetSection);
|
2023-02-08 10:07:57 +00:00
|
|
|
await driver.findContent('.test-select-row', summarySection).click();
|
|
|
|
await gu.waitForServer();
|
|
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
await gu.getVisibleGridCells({
|
|
|
|
section: summarySection,
|
|
|
|
cols: [...groubyColumns, 'rownum'],
|
|
|
|
rowNums: _.range(1, targetData.length + 1)
|
|
|
|
}),
|
|
|
|
summaryData,
|
|
|
|
);
|
|
|
|
|
|
|
|
async function checkTargetGroup(targetGroupIndex: number) {
|
|
|
|
const targetGroup = targetData[targetGroupIndex];
|
|
|
|
const countCell = await gu.getCell({section: summarySection, col: 'count', rowNum: targetGroupIndex + 1});
|
|
|
|
const numTargetRows = targetGroup.length / 3;
|
|
|
|
await countCell.click();
|
|
|
|
assert.deepEqual(
|
|
|
|
await gu.getVisibleGridCells({
|
|
|
|
section: targetSection,
|
|
|
|
cols: ['onetwo', 'choices', 'rownum'],
|
|
|
|
rowNums: _.range(1, numTargetRows + 1),
|
|
|
|
}),
|
|
|
|
targetGroup
|
|
|
|
);
|
2023-07-19 17:37:22 +00:00
|
|
|
if (targetSection === 'TABLE1') {
|
|
|
|
assert.equal(await countCell.getText(), numTargetRows.toString());
|
2023-07-31 20:10:59 +00:00
|
|
|
const csvCells = await gu.downloadSectionCsvGridCells(targetSection, headers);
|
2023-07-19 17:37:22 +00:00
|
|
|
// visible cells text uses newlines to separate list items, CSV export uses commas
|
|
|
|
const expectedCsvCells = targetGroup.map(s => s.replace("\n", ", "));
|
|
|
|
assert.deepEqual(csvCells, expectedCsvCells);
|
|
|
|
}
|
2023-02-08 10:07:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < targetData.length; i++) {
|
|
|
|
await checkTargetGroup(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (targetSection === 'TABLE1') {
|
|
|
|
// Check recursiveMoveToCursorPos
|
|
|
|
for (let rowNum = 1; rowNum <= 8; rowNum++) {
|
|
|
|
// Click an anchor link
|
|
|
|
const anchorCell = gu.getCell({section: "Anchors", rowNum, col: 1});
|
2023-07-31 20:10:59 +00:00
|
|
|
await driver.withActions(a => a.click(anchorCell.find('.test-tb-link')));
|
2023-02-08 10:07:57 +00:00
|
|
|
|
|
|
|
// Check that navigation to the link target worked
|
|
|
|
assert.equal(await gu.getActiveSectionTitle(), "TABLE1");
|
|
|
|
assert.equal(await gu.getActiveCell().getText(), String(rowNum));
|
|
|
|
|
|
|
|
// Check that the link target is still filtered correctly by the link source,
|
|
|
|
// which should imply that the link source cursor is in the right place
|
|
|
|
await gu.selectSectionByTitle(summarySection);
|
|
|
|
const summaryRowNum = await gu.getSelectedRowNum();
|
|
|
|
await checkTargetGroup(summaryRowNum - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|