2023-08-08 18:56:40 +00:00
|
|
|
/**
|
|
|
|
* Test various error situations with linking page widgets.
|
|
|
|
*/
|
2023-12-20 09:43:58 +00:00
|
|
|
import {assert, driver} from 'mocha-webdriver';
|
2023-08-08 18:56:40 +00:00
|
|
|
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());
|
|
|
|
|
2023-12-20 09:43:58 +00:00
|
|
|
before(async function() {
|
2023-08-08 18:56:40 +00:00
|
|
|
session = await gu.session().teamSite.login();
|
|
|
|
api = session.createHomeApi();
|
2023-12-20 09:43:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
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() {
|
2023-08-08 18:56:40 +00:00
|
|
|
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();
|
2023-12-20 09:43:58 +00:00
|
|
|
await gu.openWidgetPanel();
|
2023-08-08 18:56:40 +00:00
|
|
|
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});
|
2023-11-20 00:46:32 +00:00
|
|
|
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]);
|
2023-08-08 18:56:40 +00:00
|
|
|
|
|
|
|
// 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],
|
2023-11-20 00:46:32 +00:00
|
|
|
['UpdateRecord', '_grist_Views_section', 4, {linkSrcSectionRef: 1, linkSrcColRef: 0, linkTargetColRef: 0}],
|
|
|
|
['UpdateRecord', '_grist_Views_section', 8, {linkSrcSectionRef: 1, linkSrcColRef: 0, linkTargetColRef: 6}],
|
2023-08-08 18:56:40 +00:00
|
|
|
[
|
|
|
|
'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();
|
|
|
|
});
|
2023-12-20 09:43:58 +00:00
|
|
|
|
2023-08-08 18:56:40 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
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]);
|
|
|
|
}
|
2023-12-20 09:43:58 +00:00
|
|
|
|
|
|
|
// 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', '']);
|
|
|
|
}
|