gristlabs_grist-core/test/nbrowser/LinkingErrors.ts
Jarosław Sadziński 337757d0ba (core) Fix for linking issue.
Summary:
If linking state changes multiple times frequently the code that simulates async operation is
wrongly debounced, which causes inverted order of execution. This fix makes sure that only the last
call to filter function is used.

Test Plan: Adding new test

Reviewers: alexmojaki

Reviewed By: alexmojaki

Subscribers: alexmojaki

Differential Revision: https://phab.getgrist.com/D4139
2023-12-20 12:57:41 +01:00

253 lines
10 KiB
TypeScript

/**
* Test various error situations with linking page widgets.
*/
import {assert, driver} from 'mocha-webdriver';
import * as gu from 'test/nbrowser/gristUtils';
import {setupTestSuite} from 'test/nbrowser/testUtils';
import {toTableDataAction} from 'app/common/DocActions';
import {schema} from 'app/common/schema';
import {TableData} from 'app/common/TableData';
import {DocAPI, UserAPI} from 'app/common/UserAPI';
describe("LinkingErrors", function() {
this.timeout(20000);
const cleanup = setupTestSuite();
let session: gu.Session;
let api: UserAPI;
let docId: string;
afterEach(() => gu.checkForErrors());
before(async function() {
session = await gu.session().teamSite.login();
api = session.createHomeApi();
});
it("should maintain links after reload", async function() {
await session.tempNewDoc(cleanup);
// Recreate the bug.
await gu.sendActions([
['AddEmptyTable', 'Table2'], // NOTICE: The order here matters, the bug was all about ordering.
['AddEmptyTable', 'Table0'],
['ModifyColumn', 'Table1', 'B', {type: 'Ref:Table0'}],
['ModifyColumn', 'Table2', 'A', {type: 'Ref:Table1'}],
['AddRecord', 'Table0', null, {A: 'A'}],
['AddRecord', 'Table0', null, {A: 'B'}],
['AddRecord', 'Table1', null, {A: 'a', B: 1}],
['AddRecord', 'Table1', null, {A: 'b', B: 1}],
['AddRecord', 'Table1', null, {A: 'c', B: 1}],
['AddRecord', 'Table1', null, {A: 'd', B: 1}],
['AddRecord', 'Table2', null, {A: 2, B: 1}],
['AddRecord', 'Table2', null, {A: 2, B: 2}],
['AddRecord', 'Table2', null, {A: 4, B: 3}],
['AddRecord', 'Table2', null, {A: 4, B: 4}],
['AddRecord', 'Table2', null, {A: 1, B: 5}],
['AddRecord', 'Table2', null, {A: 1, B: 6}],
['AddRecord', 'Table2', null, {A: 3, B: 7}],
['AddRecord', 'Table2', null, {A: 3, B: 8}],
]);
// Now add those two tables to a page, and link them.
// Pay attention to order.
await gu.addNewPage('Table', 'Table1');
await gu.getCell('B', 1).click();
await gu.openColumnPanel();
await gu.setRefShowColumn('A');
await gu.addNewSection('Table', 'Table2', {selectBy: 'TABLE1'});
await gu.getCell('A', 1).click();
await gu.openColumnPanel();
await gu.setRefShowColumn('A');
await gu.addNewSection('Table', 'Table0');
await gu.selectSectionByTitle('TABLE1');
await gu.openWidgetPanel('data');
await gu.selectBy('TABLE0');
// Place cursor on the first row of Table0
await gu.getCell({section: 'Table1', rowNum: 1, col: 'A'}).click();
const checkLink = async () => {
// This should show the linked rows in Table1
assert.deepEqual(await gu.getVisibleGridCells({section: 'Table1', col: 'A', rowNums: [1, 2, 3, 4]}),
['a', 'b', 'c', 'd']);
// This should show the linked rows in Table2
assert.deepEqual(await gu.getVisibleGridCells({section: 'Table2', col: 'B', rowNums: [1, 2]}),
['5', '6']);
};
await checkLink();
// After reloading, we should see the same thing.
await gu.reloadDoc();
await gu.waitToPass(async () => {
// Linking is done asynchronously, so make sure it is loaded first.
assert.equal(await gu.selectedBy(), 'TABLE0');
await checkLink();
});
});
it("should link correctly in the normal case", async function() {
docId = await session.tempNewDoc(cleanup, 'LinkingErrors1', {load: false});
// Make a table with some data.
await api.applyUserActions(docId, [
['AddTable', 'Planets', [{id: 'PlanetName'}]],
// Negative IDs allow referrnig to added records in the same action bundle.
['AddRecord', 'Planets', -1, {PlanetName: 'Earth'}],
['AddRecord', 'Planets', -2, {PlanetName: 'Mars'}],
['AddTable', 'Moons', [{id: 'MoonName'}, {id: 'Planet', type: 'Ref:Planets'}]],
['AddRecord', 'Moons', null, {MoonName: 'Phobos', Planet: -2}],
['AddRecord', 'Moons', null, {MoonName: 'Deimos', Planet: -2}],
['AddRecord', 'Moons', null, {MoonName: 'Moon', Planet: -1}],
]);
// Load the document.
await session.loadDoc(`/doc/${docId}`);
await gu.getPageItem('Planets').click();
await gu.waitForDocToLoad();
await gu.addNewSection(/Table/, /Moons/, {selectBy: /PLANETS/});
await checkLinking();
});
it("should unset linking setting when changing the data table for a widget", async function() {
await gu.getCell({section: 'Moons', rowNum: 1, col: 'MoonName'}).click();
await gu.openWidgetPanel();
await driver.findContent('.test-right-panel button', /Change Widget/).click();
assert.equal(await driver.find('.test-wselect-table-label[class*=-selected]').getText(), 'Moons');
await driver.findContent('.test-wselect-table', /Planets/).click();
assert.match(await driver.find('.test-wselect-selectby').value(), /Select Widget/);
await driver.find('.test-wselect-addBtn').doClick();
await gu.waitForServer();
// Check that the two sections on the page are now for the same table, and link settings are
// cleared.
const tables = await getTableData(api.getDocAPI(docId), '_grist_Tables');
const sections = await getTableData(api.getDocAPI(docId), '_grist_Views_section');
const planetsTable = tables.filterRecords({tableId: 'Planets'})[0];
assert.isOk(planetsTable);
const planetsSections = sections.filterRecords({tableRef: planetsTable.id});
assert.lengthOf(planetsSections, 4);
assert.equal(planetsSections[0].parentId, planetsSections[3].parentId);
assert.deepEqual(planetsSections.map(s => s.linkTargetColRef), [0, 0, 0, 0]);
assert.deepEqual(planetsSections.map(s => s.linkSrcSectionRef), [0, 0, 0, 0]);
assert.deepEqual(planetsSections.map(s => s.linkSrcColRef), [0, 0, 0, 0]);
// Switch to another page and back and check that there are no errors.
await gu.getPageItem('Moons').click();
await gu.checkForErrors();
await gu.getPageItem('Planets').click();
await gu.checkForErrors();
// Now undo.
await gu.undo();
// Still should have no errors, and linking should be restored.
await gu.checkForErrors();
await checkLinking();
});
it("should fail to link gracefully when linking settings are wrong", async function() {
// Fetch link settings, then mess them up.
const columns = await getTableData(api.getDocAPI(docId), '_grist_Tables_column');
const sections = await getTableData(api.getDocAPI(docId), '_grist_Views_section');
const planetRefCol = columns.filterRecords({colId: 'Planet'})[0]; // In table Moons
const planetNameCol = columns.filterRecords({colId: 'PlanetName'})[0]; // In table Planets
assert.isOk(planetRefCol);
assert.isOk(planetNameCol);
const planetSec = sections.filterRecords({linkTargetColRef: planetRefCol.id})[0];
assert.isOk(planetSec);
// Set invalid linking. The column we are setting is for the wrong table. It used to happen
// occasionally due to other bugs, here we check that we ignore such invalid settings.
await api.applyUserActions(docId, [
['UpdateRecord', '_grist_Views_section', planetSec.id, {linkTargetColRef: planetNameCol.id}]
]);
// Reload the page.
await driver.navigate().refresh();
await gu.waitForDocToLoad();
// Expect no errors, and expect to see data, although it's no longer linked.
await gu.checkForErrors();
await gu.getCell({section: 'PLANETS', rowNum: 1, col: 'PlanetName'}).click();
assert.deepEqual(await gu.getVisibleGridCells({section: 'MOONS', col: 'MoonName', rowNums: [1, 2, 3, 4]}),
['Phobos', 'Deimos', 'Moon', '']);
// Reverting to correct settings should make the data linked again.
await api.applyUserActions(docId, [
['UpdateRecord', '_grist_Views_section', planetSec?.id, {linkTargetColRef: planetRefCol.id}]
]);
await gu.checkForErrors();
await checkLinking();
});
it("should recreate page with undo", async function() {
const tempDoc = await session.tempNewDoc(cleanup, 'LinkingErrors2', {load: false});
// This tests the bug: When removing page with linked sections and then undoing, there are two JS errors raised:
// - flexSize is not a function
// - getter is not a function
// To recreate create a simple document:
// - Table1 with default columns
// - Table2 with A being reference to Table1
// - For Table1 page add:
// -- Single card view selected by Table1
// -- Table2 view selected by Table1
// And make some updates that will cause a bug (without it undoing works).
// Modify the layout for page Table1, this makes the first JS bug (flexSize ...) when undoing.
// And add some records, which makes the second JS bug (getter is not a function).
const actions = [
['CreateViewSection', 1, 1, 'single', null, null],
['AddEmptyTable', null],
['UpdateRecord', '_grist_Tables_column', 6, {type: 'Ref:Table1'}],
['CreateViewSection', 2, 1, 'record', null, null],
['UpdateRecord', '_grist_Views_section', 4, {linkSrcSectionRef: 1, linkSrcColRef: 0, linkTargetColRef: 0}],
['UpdateRecord', '_grist_Views_section', 8, {linkSrcSectionRef: 1, linkSrcColRef: 0, linkTargetColRef: 6}],
[
'UpdateRecord',
'_grist_Views',
1,
{layoutSpec: '{"children":[{"children":[{"leaf":1},{"children":[{"leaf":2},{"leaf":4}]}]}]}'},
],
['AddRecord', 'Table1', null, {A: '1'}],
['AddRecord', 'Table2', null, {A: 1, B: '2'}],
];
await api.applyUserActions(tempDoc, actions);
// Load the document.
await session.loadDoc(`/doc/${tempDoc}`);
const revert = await gu.begin();
// Remove the first page (and Table1).
await gu.removePage('Table1', {withData: true});
await gu.waitForServer();
// And do undo
await revert();
await gu.checkForErrors();
});
});
async function getTableData(docApi: DocAPI, tableId: string): Promise<TableData> {
const colValues = await docApi.getRows(tableId);
const tableAction = toTableDataAction(tableId, colValues);
return new TableData(tableId, tableAction, (schema as any)[tableId]);
}
// Check that normal linking works.
async function checkLinking() {
await gu.getCell({section: 'PLANETS', rowNum: 1, col: 'PlanetName'}).click();
assert.deepEqual(await gu.getVisibleGridCells({section: 'MOONS', col: 'MoonName', rowNums: [1, 2]}),
['Moon', '']);
await gu.getCell({section: 'PLANETS', rowNum: 2, col: 'PlanetName'}).click();
assert.deepEqual(await gu.getVisibleGridCells({section: 'MOONS', col: 'MoonName', rowNums: [1, 2, 3]}),
['Phobos', 'Deimos', '']);
}