mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) move LinkingError and RawData tests
Summary: Moves some more relevant tests to grist-core. Small change to declarations that seemed necessary when running buildtools/build_core.sh now, but I'm not sure what changed. CI hasn't flagged anything, so smells like the version of some package not nailed down. Test Plan: tried running tests in new location using buildtools/build_core.sh Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3996
This commit is contained in:
parent
732611c356
commit
d6ab1f45c2
11
test/declarations.d.ts
vendored
11
test/declarations.d.ts
vendored
@ -1,4 +1,13 @@
|
||||
declare module "test/nbrowser/gristUtil-nbrowser";
|
||||
declare module "test/nbrowser/gristUtil-nbrowser" {
|
||||
// TODO - tsc can now do nice type inference for most of this, except $,
|
||||
// so could change how export is done. Right now it leads to a mess because
|
||||
// of $.
|
||||
export declare let $: any;
|
||||
export declare let gu: any;
|
||||
export declare let server: any;
|
||||
export declare let test: any;
|
||||
}
|
||||
|
||||
|
||||
// Adds missing type declaration to chai
|
||||
declare namespace Chai {
|
||||
|
182
test/nbrowser/LinkingErrors.ts
Normal file
182
test/nbrowser/LinkingErrors.ts
Normal file
@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Test various error situations with linking page widgets.
|
||||
*/
|
||||
import {assert, driver, stackWrapFunc} 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());
|
||||
|
||||
it("should link correctly in the normal case", async function() {
|
||||
session = await gu.session().teamSite.login();
|
||||
api = session.createHomeApi();
|
||||
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();
|
||||
});
|
||||
|
||||
// Check that normal linking works.
|
||||
const checkLinking = stackWrapFunc(async function() {
|
||||
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', '']);
|
||||
});
|
||||
|
||||
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.toggleSidePanel('right', 'open');
|
||||
await driver.find('.test-right-tab-pagewidget').click();
|
||||
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, 3);
|
||||
assert.equal(planetsSections[0].parentId, planetsSections[2].parentId);
|
||||
assert.deepEqual(planetsSections.map(s => s.linkTargetColRef), [0, 0, 0]);
|
||||
assert.deepEqual(planetsSections.map(s => s.linkSrcSectionRef), [0, 0, 0]);
|
||||
assert.deepEqual(planetsSections.map(s => s.linkSrcColRef), [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', 3, {linkSrcSectionRef: 1, linkSrcColRef: 0, linkTargetColRef: 0}],
|
||||
['UpdateRecord', '_grist_Views_section', 6, {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]);
|
||||
}
|
701
test/nbrowser/RawData.ts
Normal file
701
test/nbrowser/RawData.ts
Normal file
@ -0,0 +1,701 @@
|
||||
import {UserAPI} from 'app/common/UserAPI';
|
||||
import {assert, driver, Key} from 'mocha-webdriver';
|
||||
import * as gu from 'test/nbrowser/gristUtils';
|
||||
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
|
||||
|
||||
describe('RawData', function () {
|
||||
this.timeout(30000);
|
||||
let api: UserAPI;
|
||||
let doc: string;
|
||||
// We will stress undo here and will try to undo all tests that were using RAW DATA views.
|
||||
// At the time of writing this test, undo was basically not possible and was throwing all sort
|
||||
// of exceptions (related to summary tables).
|
||||
let revertAll: () => Promise<void>;
|
||||
setupTestSuite();
|
||||
gu.bigScreen();
|
||||
afterEach(() => gu.checkForErrors());
|
||||
before(async function () {
|
||||
await server.simulateLogin('Chimpy', 'chimpy@getgrist.com', 'nasa');
|
||||
const docInfo = await gu.importFixturesDoc('chimpy', 'nasa', 'Horizon', 'World.grist');
|
||||
doc = docInfo.id;
|
||||
api = gu.createHomeApi('Chimpy', 'nasa');
|
||||
await openRawData();
|
||||
revertAll = await gu.begin();
|
||||
});
|
||||
|
||||
it('shows all tables', async function () {
|
||||
const uiTables = await getRawTableIds();
|
||||
const data = await api.getTable(doc, '_grist_Tables');
|
||||
const tables: string[] = data.tableId as string[];
|
||||
tables.sort();
|
||||
uiTables.sort();
|
||||
assert.deepEqual(uiTables, tables);
|
||||
});
|
||||
|
||||
it('shows row counts of all tables', async function () {
|
||||
assert.deepEqual(await getRawTableRows(), [
|
||||
'4,079',
|
||||
'239',
|
||||
'984',
|
||||
'4',
|
||||
]);
|
||||
});
|
||||
|
||||
it('shows new table name', async function () {
|
||||
await gu.renameTable('City', 'Town');
|
||||
const uiTables = await getRawTableIds();
|
||||
const data = await api.getTable(doc, '_grist_Tables');
|
||||
const tables: string[] = data.tableId as string[];
|
||||
tables.sort();
|
||||
uiTables.sort();
|
||||
assert.deepEqual(uiTables, tables);
|
||||
});
|
||||
|
||||
it('shows table preview', async function () {
|
||||
// Open modal with grid
|
||||
await driver.findContent('.test-raw-data-table-title', 'Country').click();
|
||||
await gu.waitForServer();
|
||||
// Test that overlay is showed.
|
||||
assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed());
|
||||
// Test proper table is selected.
|
||||
assert.equal(await gu.getSectionTitle(), 'Country');
|
||||
// Test we have some data.
|
||||
assert.deepEqual(await gu.getVisibleGridCells('Code', [1, 2], 'Country'), ['ABW', 'AFG']);
|
||||
// Test we can close by button.
|
||||
await gu.closeRawTable();
|
||||
assert.isFalse(await driver.find('.test-raw-data-overlay').isPresent());
|
||||
|
||||
// Test we can close by pressing escape.
|
||||
await driver.findContent('.test-raw-data-table-title', 'Country').click();
|
||||
assert.isTrue(await driver.find('.test-raw-data-overlay').isDisplayed());
|
||||
await driver.sendKeys(Key.ESCAPE);
|
||||
assert.isFalse(await driver.find('.test-raw-data-overlay').isPresent());
|
||||
|
||||
// Test we can't close by pressing escape when there is a selection,
|
||||
await driver.findContent('.test-raw-data-table-title', 'Country').click();
|
||||
assert.isTrue(await driver.find('.test-raw-data-overlay').isDisplayed());
|
||||
await driver.find('.gridview_data_corner_overlay').doClick();
|
||||
await driver.sendKeys(Key.ESCAPE);
|
||||
assert.isTrue(await driver.find('.test-raw-data-overlay').isDisplayed());
|
||||
// Press ESCAPE one more time to close.
|
||||
await driver.sendKeys(Key.ESCAPE);
|
||||
assert.isFalse(await driver.find('.test-raw-data-overlay').isPresent());
|
||||
|
||||
// Test we can close by clicking on overlay.
|
||||
await driver.findContent('.test-raw-data-table-title', 'Country').click();
|
||||
assert.isTrue(await driver.find('.test-raw-data-overlay').isDisplayed());
|
||||
await driver.find('.test-raw-data-close-button').mouseMove();
|
||||
await driver.mouseMoveBy({y: 100}); // move 100px below (not negative value)
|
||||
await driver.withActions(a => a.click());
|
||||
assert.isFalse(await driver.find('.test-raw-data-overlay').isPresent());
|
||||
});
|
||||
|
||||
it('should rename table from modal window', async function () {
|
||||
// Open Country table.
|
||||
await driver.findContent('.test-raw-data-table-title', 'Country').click();
|
||||
await gu.waitForServer();
|
||||
// Rename section to Empire
|
||||
await gu.renameActiveTable('Empire');
|
||||
// Close and test that it was renamed
|
||||
await gu.closeRawTable();
|
||||
const tables = await getRawTableIds();
|
||||
const titles = await driver.findAll('.test-raw-data-table-title', e => e.getText());
|
||||
tables.sort();
|
||||
titles.sort();
|
||||
// Title should also be renamed for now. In follow-up diff those
|
||||
// two will be separate.
|
||||
assert.deepEqual(titles, ['Town', 'Empire', 'CountryLanguage', 'Table1'].sort());
|
||||
assert.deepEqual(tables, ['Town', 'Empire', 'CountryLanguage', 'Table1'].sort());
|
||||
});
|
||||
|
||||
it('should remove table', async function () {
|
||||
// Open menu for Town
|
||||
await openMenu('Town');
|
||||
// Click delete.
|
||||
await clickRemove();
|
||||
// Confirm.
|
||||
await clickConfirm();
|
||||
await gu.waitForServer();
|
||||
const tables = await getRawTableIds();
|
||||
const titles = await driver.findAll('.test-raw-data-table-title', e => e.getText());
|
||||
tables.sort();
|
||||
titles.sort();
|
||||
// Title should also be renamed for now. In a follow-up diff those
|
||||
// two will be separate.
|
||||
assert.deepEqual(titles, ['Empire', 'CountryLanguage', 'Table1'].sort());
|
||||
assert.deepEqual(tables, ['Empire', 'CountryLanguage', 'Table1'].sort());
|
||||
});
|
||||
|
||||
it('should duplicate table', async function () {
|
||||
await openMenu('Empire');
|
||||
await clickDuplicateTable();
|
||||
await driver.find('.test-duplicate-table-name').click();
|
||||
await gu.sendKeys('Empire Copy');
|
||||
|
||||
// Before clicking the Copy All Data checkbox, check that no warning about ACLs is shown.
|
||||
assert.isFalse(await driver.find('.test-duplicate-table-acl-warning').isPresent());
|
||||
|
||||
// Now click the Copy All Data checkbox, and check that the warning is shown.
|
||||
await driver.find('.test-duplicate-table-copy-all-data').click();
|
||||
assert.isTrue(await driver.find('.test-duplicate-table-acl-warning').isPresent());
|
||||
|
||||
await clickConfirm();
|
||||
await gu.waitForServer();
|
||||
assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed());
|
||||
assert.equal(await gu.getSectionTitle(), 'Empire Copy');
|
||||
assert.deepEqual(await gu.getVisibleGridCells('Code', [1, 2], 'Empire Copy'), ['ABW', 'AFG']);
|
||||
|
||||
await driver.sendKeys(Key.ESCAPE);
|
||||
const tables = await getRawTableIds();
|
||||
const titles = await driver.findAll('.test-raw-data-table-title', e => e.getText());
|
||||
tables.sort();
|
||||
titles.sort();
|
||||
assert.deepEqual(titles, ['Empire', 'Empire Copy', 'CountryLanguage', 'Table1'].sort());
|
||||
assert.deepEqual(tables, ['Empire', 'Empire_Copy', 'CountryLanguage', 'Table1'].sort());
|
||||
});
|
||||
|
||||
it('should restore position when browser is refreshed', async function () {
|
||||
await driver.findContent('.test-raw-data-table-title', 'Empire').click();
|
||||
await gu.waitForServer();
|
||||
await gu.getCell(3, 2).click();
|
||||
await driver.navigate().refresh();
|
||||
await gu.waitForDocToLoad();
|
||||
assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed());
|
||||
assert.deepEqual(await gu.getCursorPosition(), {col: 3, rowNum: 2});
|
||||
// Close overlay.
|
||||
await driver.sendKeys(Key.ESCAPE);
|
||||
});
|
||||
|
||||
it('should restore last edit position when browser is refreshed', async function () {
|
||||
await driver.findContent('.test-raw-data-table-title', 'Empire').click();
|
||||
await gu.waitForServer();
|
||||
await gu.getCell(2, 9).click();
|
||||
await driver.sendKeys('123456789');
|
||||
await gu.refreshDismiss();
|
||||
await gu.waitForDocToLoad();
|
||||
assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed());
|
||||
await gu.checkTextEditor(gu.exactMatch('123456789'));
|
||||
// Close editor.
|
||||
await driver.sendKeys(Key.ESCAPE);
|
||||
assert.deepEqual(await gu.getCursorPosition(), {col: 2, rowNum: 9});
|
||||
// Close overlay.
|
||||
await driver.sendKeys(Key.ESCAPE);
|
||||
});
|
||||
|
||||
it('should copy anchor link and restore', async function () {
|
||||
await driver.findContent('.test-raw-data-table-title', 'Empire').click();
|
||||
await gu.waitForServer();
|
||||
await (await gu.openRowMenu(10)).findContent('li', /Copy anchor link/).click();
|
||||
await driver.findContentWait('.test-notifier-toast-message', /Link copied to clipboard/, 2000);
|
||||
await driver.find('.test-notifier-toast-close').click();
|
||||
const anchor = (await gu.getTestState()).clipboard!;
|
||||
await gu.getCell(3, 2).click();
|
||||
await gu.onNewTab(async () => {
|
||||
await driver.get(anchor);
|
||||
await gu.waitForAnchor();
|
||||
assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed());
|
||||
assert.deepEqual(await gu.getCursorPosition(), {col: 0, rowNum: 10});
|
||||
});
|
||||
// Close overlay.
|
||||
await driver.sendKeys(Key.ESCAPE);
|
||||
});
|
||||
|
||||
it('should copy table name', async function () {
|
||||
await driver.findContentWait('.test-raw-data-table-id', 'Empire', 1000).click();
|
||||
await gu.waitToPass(async () => {
|
||||
assert.equal((await gu.getTestState()).clipboard, 'Empire');
|
||||
}, 500);
|
||||
// Currently tooltip is not dismissible, so let's refresh the page.
|
||||
await driver.navigate().refresh();
|
||||
await waitForRawData();
|
||||
});
|
||||
|
||||
it('shows summary tables under Raw Data Tables', async function () {
|
||||
// Add a few summary tables: 1 with no group-by columns, and 2 that
|
||||
// share the same group-by columns.
|
||||
for (let i = 0; i <= 2; i++) {
|
||||
await gu.addNewPage(/Table/, /CountryLanguage/, {
|
||||
summarize: i === 0 ? [] : ['Country']
|
||||
});
|
||||
}
|
||||
|
||||
// Check that the added summary tables are listed at the end.
|
||||
await openRawData();
|
||||
assert.deepEqual(await getRawTableTitles(), [
|
||||
'CountryLanguage',
|
||||
'Empire',
|
||||
'Empire Copy',
|
||||
'Table1',
|
||||
'CountryLanguage [Totals]',
|
||||
'CountryLanguage [by Country]',
|
||||
]);
|
||||
assert.deepEqual(await getRawTableIds(), [
|
||||
'CountryLanguage',
|
||||
'Empire',
|
||||
'Empire_Copy',
|
||||
'Table1',
|
||||
'CountryLanguage_summary',
|
||||
'CountryLanguage_summary_Country',
|
||||
]);
|
||||
});
|
||||
|
||||
it('shows preview of summary table when clicked', async function () {
|
||||
// Open a summary table.
|
||||
await driver.findContent('.test-raw-data-table-title', 'CountryLanguage [by Country]').click();
|
||||
await gu.waitForServer();
|
||||
|
||||
// Check that an overlay is shown.
|
||||
assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed());
|
||||
|
||||
// Check that the right section title is shown.
|
||||
assert.equal(await gu.getSectionTitle(), 'COUNTRYLANGUAGE [by Country]');
|
||||
|
||||
// Make sure the data looks correct.
|
||||
assert.deepEqual(
|
||||
await gu.getVisibleGridCells('Country', [1, 2, 3, 4, 5], 'CountryLanguage [by Country]'),
|
||||
['ABW', 'AFG', 'AGO', 'AIA', 'ALB'],
|
||||
);
|
||||
|
||||
// Close the overlay.
|
||||
await gu.closeRawTable();
|
||||
assert.isFalse(await driver.find('.test-raw-data-overlay').isPresent());
|
||||
});
|
||||
|
||||
it('removes summary table when all sections referencing it are removed', async function () {
|
||||
// CountryLanguage [Totals] and CountryLanguage [by Country] respectively.
|
||||
await gu.removePage('New page');
|
||||
await gu.removePage('New page');
|
||||
|
||||
// Check that the table summarizing by country wasn't removed, since there is still
|
||||
// one more view for it.
|
||||
assert.deepEqual(await getRawTableTitles(), [
|
||||
'CountryLanguage',
|
||||
'Empire',
|
||||
'Empire Copy',
|
||||
'Table1',
|
||||
'CountryLanguage [by Country]',
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes summary table when source table is removed', async function () {
|
||||
await removeRawTable('CountryLanguage');
|
||||
assert.deepEqual(await getRawTableTitles(), [
|
||||
'Empire',
|
||||
'Empire Copy',
|
||||
'Table1',
|
||||
]);
|
||||
await gu.undo();
|
||||
assert.deepEqual(await getRawTableTitles(), [
|
||||
'CountryLanguage',
|
||||
'Empire',
|
||||
'Empire Copy',
|
||||
'Table1',
|
||||
'CountryLanguage [by Country]',
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes summary table when "Remove" menu item is clicked', async function () {
|
||||
const tableIds = await getRawTableIds();
|
||||
await removeRawTable(tableIds[tableIds.length - 1]);
|
||||
|
||||
const titles = await getRawTableTitles();
|
||||
assert.deepEqual(titles, [
|
||||
'CountryLanguage',
|
||||
'Empire',
|
||||
'Empire Copy',
|
||||
'Table1',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should stay on a page when undoing summary table', async function () {
|
||||
// Undoing after converting a table to a summary table doesn't know
|
||||
// where to navigate, as section is removed and recreated during navigation
|
||||
// and it is not connected to any view for a brief moment - which makes that
|
||||
// section look like a Raw Data View (section without a view).
|
||||
|
||||
// This tests that the section is properly identified and Grist will not navigate
|
||||
// to the Raw Data view.
|
||||
await gu.addNewTable();
|
||||
const url = await driver.getCurrentUrl();
|
||||
assert.isTrue(url.endsWith('p/8'));
|
||||
await convertToSummary();
|
||||
assert.equal(url, await driver.getCurrentUrl());
|
||||
await gu.undo();
|
||||
assert.equal(url, await driver.getCurrentUrl());
|
||||
await gu.redo();
|
||||
// Reverting actually went to a bare document url (without a page id)
|
||||
// This was old buggy behavior that is now fixed.
|
||||
|
||||
assert.equal(url, await driver.getCurrentUrl());
|
||||
assert.deepEqual(await gu.getCursorPosition(), {rowNum: 1, col: 0});
|
||||
|
||||
// Switching pages was producing error after undoing summary table.
|
||||
await gu.openPage('Empire');
|
||||
await gu.checkForErrors();
|
||||
await gu.openPage('Table2');
|
||||
await gu.checkForErrors();
|
||||
});
|
||||
|
||||
it('should remove all tables except one (including referenced summary table)', async function () {
|
||||
// First we will add a new summary table for CountryLanguage table.
|
||||
// This table has a reference to the Country table, and Grist had a bug that
|
||||
// didn't allow to delete those tables - so here we will test if this is fixed.
|
||||
await gu.addNewPage('Table', 'CountryLanguage', {
|
||||
summarize: ['Country'],
|
||||
});
|
||||
|
||||
await openRawData();
|
||||
|
||||
const allTables = await getRawTableIds();
|
||||
|
||||
// Now we are ready to test deletion.
|
||||
const beforeDeleteCheckpoint = await gu.begin();
|
||||
|
||||
// First remove that table without a raw section, to see if that works.
|
||||
await removeRawTable('Table1');
|
||||
await gu.checkForErrors();
|
||||
assert.isFalse((await getRawTableIds()).includes('Table1'));
|
||||
|
||||
// Now try to remove Country (now Empire) table - here we had a bug
|
||||
await removeRawTable('Empire');
|
||||
await gu.checkForErrors();
|
||||
assert.isFalse((await getRawTableIds()).includes('Empire'));
|
||||
|
||||
// Now revert and remove all until remove is disabled
|
||||
await beforeDeleteCheckpoint();
|
||||
await openRawData();
|
||||
|
||||
while (allTables.length > 1) {
|
||||
await removeRawTable(allTables.pop()!);
|
||||
}
|
||||
|
||||
// We should have only one table
|
||||
assert.deepEqual(await getRawTableIds(), allTables);
|
||||
|
||||
// The last table should have disabled remove button.
|
||||
await openMenu(allTables[0]);
|
||||
assert.isTrue(await driver.find('.test-raw-data-menu-remove.disabled').isDisplayed());
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
});
|
||||
|
||||
it('should allow removing GristHidden* pages', async () => {
|
||||
// Add a table named GristHidden_test, to test when such tables are left over after an incomplete import.
|
||||
|
||||
// Prepare two tables to test
|
||||
await gu.addNewTable();
|
||||
// Remove last old table we have
|
||||
await openRawData();
|
||||
await removeRawTable('CountryLanguage');
|
||||
|
||||
await gu.addNewTable();
|
||||
assert.deepEqual(await gu.getPageNames(), ['Table1', 'Table2']);
|
||||
|
||||
// Rename Table2 page to GristHidden_test, it should be still visible, as the table
|
||||
// id is diffrent (not hidden).
|
||||
await gu.renamePage('Table1', 'GristHidden_test');
|
||||
assert.deepEqual(await gu.getPageNames(), ['GristHidden_test', 'Table2']);
|
||||
// Make sure all pages can be removed
|
||||
for (const page of await gu.getPageNames()) {
|
||||
assert.isTrue(await gu.canRemovePage(page));
|
||||
}
|
||||
await gu.removePage('Table2');
|
||||
assert.deepEqual(await gu.getPageNames(), ['GristHidden_test']);
|
||||
assert.isFalse(await gu.canRemovePage('GristHidden_test'));
|
||||
await gu.undo();
|
||||
|
||||
await gu.removePage('GristHidden_test');
|
||||
assert.deepEqual(await gu.getPageNames(), ['Table2']);
|
||||
assert.isFalse(await gu.canRemovePage('Table2'));
|
||||
await gu.undo();
|
||||
assert.deepEqual(await gu.getPageNames(), ['GristHidden_test', 'Table2']);
|
||||
});
|
||||
|
||||
it('should allow removing hidden tables', async () => {
|
||||
// Rename Table1 table to a simulate hidden table
|
||||
await openRawData();
|
||||
await gu.renameRawTable("Table2", "GristHidden_import");
|
||||
assert.deepEqual(await getRawTableIds(), ['GristHidden_import', 'Table1']);
|
||||
// Page should be hidden now
|
||||
assert.deepEqual(await gu.getPageNames(), ['GristHidden_test']);
|
||||
assert.isFalse(await gu.canRemovePage('GristHidden_test'));
|
||||
// We should be able to remove hidden table, but not user table (as this can be last table that will
|
||||
// be auto-removed).
|
||||
assert.isTrue(await isRemovable('GristHidden_import'));
|
||||
assert.isFalse(await isRemovable('Table1'));
|
||||
|
||||
// Rename back
|
||||
await gu.renameRawTable("GristHidden_import", "Table2");
|
||||
// Page should be visible again
|
||||
assert.deepEqual(await gu.getPageNames(), ['GristHidden_test', 'Table2']);
|
||||
for (const page of await gu.getPageNames()) {
|
||||
assert.isTrue(await gu.canRemovePage(page));
|
||||
}
|
||||
assert.isTrue(await isRemovable('Table2'));
|
||||
assert.isTrue(await isRemovable('Table1'));
|
||||
|
||||
// Rename once again and test if it can be actually removed.
|
||||
await gu.renameRawTable("Table2", "GristHidden_import");
|
||||
assert.isTrue(await isRemovable('GristHidden_import'));
|
||||
await removeRawTable("GristHidden_import");
|
||||
await gu.checkForErrors();
|
||||
assert.deepEqual(await getRawTableIds(), ['Table1']);
|
||||
assert.isFalse(await isRemovable('Table1'));
|
||||
});
|
||||
|
||||
it('should revert all without errors', async function () {
|
||||
// Revert internally checks errors.
|
||||
await revertAll();
|
||||
});
|
||||
|
||||
it('should open raw data as a popup', async () => {
|
||||
// We are at City table, in first row/first cell.
|
||||
// Send some keys, to make sure we have focus on active section.
|
||||
// RawData popup is manipulating what section has focus, so we need to make sure that
|
||||
// focus is properly restored.
|
||||
assert.deepEqual(await gu.getCursorPosition(), {rowNum: 1, col: 0});
|
||||
await gu.getCell(0, 2).click();
|
||||
await gu.sendKeys("abc");
|
||||
await gu.checkTextEditor("abc");
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
await showRawData();
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'City');
|
||||
assert.deepEqual(await gu.getCursorPosition(), {rowNum: 20, col: 0}); // raw popup is not sorted
|
||||
await gu.sendKeys("abc");
|
||||
await gu.checkTextEditor("abc");
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
// Click on another cell, check page hasn't changed (there was a bug about that)
|
||||
await gu.getCell({rowNum: 10, col: 1}).click();
|
||||
assert.deepEqual(await gu.getCursorPosition(), {rowNum: 10, col: 1});
|
||||
assert.equal(await gu.getCurrentPageName(), 'City');
|
||||
|
||||
// Close by hitting escape.
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
await assertNoPopup();
|
||||
// Make sure we see CITY, and everything is where it should be.
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'CITY');
|
||||
assert.deepEqual(await gu.getCursorPosition(), {rowNum: 2, col: 0});
|
||||
await gu.sendKeys("abc");
|
||||
await gu.checkTextEditor("abc");
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
|
||||
// Now open popup again, but close it by clicking on the close button.
|
||||
await showRawData();
|
||||
await gu.closeRawTable();
|
||||
await assertNoPopup();
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'CITY');
|
||||
assert.deepEqual(await gu.getCursorPosition(), {rowNum: 2, col: 0});
|
||||
await gu.sendKeys("abc");
|
||||
await gu.checkTextEditor("abc");
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
|
||||
// Now do the same, but close by clicking on a diffrent page
|
||||
await showRawData();
|
||||
await gu.getPageItem('Country').click();
|
||||
await assertNoPopup();
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'COUNTRY');
|
||||
assert.deepEqual(await gu.getCursorPosition(), {rowNum: 1, col: 0});
|
||||
await gu.sendKeys("abc");
|
||||
await gu.checkTextEditor("abc");
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
|
||||
// Now make sure that raw data is available for card view.
|
||||
await gu.selectSectionByTitle("COUNTRY Card List");
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'COUNTRY Card List');
|
||||
await showRawData();
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'Country');
|
||||
assert.deepEqual(await gu.getCursorPosition(), {rowNum: 1, col: 1});
|
||||
await gu.sendKeys("abc");
|
||||
await gu.checkTextEditor("abc");
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
// Make sure we see a grid
|
||||
assert.isTrue(await driver.find(".grid_view_data").isDisplayed());
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
});
|
||||
|
||||
// This is not documented feature at this moment, and tailored for raw data
|
||||
// view, but it should work for any kind of section.
|
||||
it('should open detail section as a popup', async () => {
|
||||
// We are at the Country page
|
||||
await gu.getDetailCell('Code', 1).click();
|
||||
let anchorLink = replaceAnchor(await gu.getAnchor(), { a: '2' });
|
||||
const testResult = async () => {
|
||||
await waitForAnchorPopup(anchorLink);
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'COUNTRY Card List');
|
||||
assert.deepEqual(await gu.getCursorPosition(), {rowNum: 1, col: 'Code'});
|
||||
await gu.sendKeys("abc");
|
||||
await gu.checkTextEditor("abc");
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
// Close by hitting escape.
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
// Make sure we are on correct page
|
||||
assert.equal(await gu.getCurrentPageName(), "City");
|
||||
};
|
||||
// Switch page and use only hash, otherwise it will just maximize the section on the current page.
|
||||
await gu.getPageItem('City').click();
|
||||
anchorLink = (await driver.getCurrentUrl()) + '#' + anchorLink.split('#')[1];
|
||||
await testResult();
|
||||
});
|
||||
|
||||
it('should open chart section as a popup', gu.revertChanges(async () => {
|
||||
// We are at the Country page
|
||||
await gu.getPageItem('Country').click();
|
||||
await gu.selectSectionByTitle("COUNTRY Card List");
|
||||
await gu.getDetailCell('Code', 1).click();
|
||||
await gu.addNewSection(/Chart/, /CountryLanguage/);
|
||||
// s19 is the new section id, we also strip row/column.
|
||||
let chartLink = replaceAnchor(await gu.getAnchor(), {s: '19', a: '2'});
|
||||
await gu.getPageItem('City').click();
|
||||
chartLink = (await driver.getCurrentUrl()) + '#' + chartLink.split('#')[1];
|
||||
await waitForAnchorPopup(chartLink);
|
||||
assert.isTrue(await driver.find(".test-raw-data-overlay .test-chart-container").isDisplayed());
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
}));
|
||||
|
||||
it('should handle edge cases when table/section is removed', async () => {
|
||||
await gu.getPageItem('Country').click();
|
||||
await gu.selectSectionByTitle("COUNTRY Card List");
|
||||
await gu.getDetailCell('Code', 1).click();
|
||||
let anchorLink = replaceAnchor(await gu.getAnchor(), { a: '2' });
|
||||
await gu.getPageItem('City').click();
|
||||
anchorLink = (await driver.getCurrentUrl()) + '#' + anchorLink.split('#')[1];
|
||||
await waitForAnchorPopup(anchorLink);
|
||||
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'COUNTRY Card List');
|
||||
// Now remove the section using api, popup should be closed.
|
||||
const sectionId = parseInt(getAnchorParams(anchorLink).s);
|
||||
await api.applyUserActions(doc, [[
|
||||
'RemoveRecord', '_grist_Views_section', sectionId
|
||||
]]);
|
||||
await gu.waitForServer();
|
||||
await gu.checkForErrors();
|
||||
await assertNoPopup();
|
||||
// Now open plain raw data for City table.
|
||||
await gu.selectSectionByTitle("CITY");
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'CITY'); // CITY is viewSection title
|
||||
await showRawData();
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'City'); // City is now a table title
|
||||
// Now remove the table.
|
||||
await api.applyUserActions(doc, [[
|
||||
'RemoveTable', 'City'
|
||||
]]);
|
||||
await gu.waitForServer();
|
||||
await gu.checkForErrors();
|
||||
await assertNoPopup();
|
||||
});
|
||||
});
|
||||
|
||||
const anchorRegex = /#a(\d+)\.s(\d+)\.r(\d+)\.c(\d+)/gm;
|
||||
|
||||
function getAnchorParams(link: string) {
|
||||
const match = anchorRegex.exec(link);
|
||||
if (!match) { throw new Error("Invalid link"); }
|
||||
const [, a, s, r, c] = match;
|
||||
return { a, s, r, c };
|
||||
}
|
||||
|
||||
function replaceAnchor(link: string, values: {
|
||||
a?: string,
|
||||
s?: string,
|
||||
r?: string,
|
||||
c?: string,
|
||||
}) {
|
||||
const { a, s, r, c } = getAnchorParams(link);
|
||||
return link.replace(anchorRegex, `#a${values.a || a}.s${values.s || s}.r${values.r || r}.c${values.c || c}`);
|
||||
}
|
||||
|
||||
async function showRawData() {
|
||||
await gu.openSectionMenu('viewLayout');
|
||||
await driver.find('.test-show-raw-data').click();
|
||||
await waitForPopup();
|
||||
}
|
||||
|
||||
async function openRawData() {
|
||||
await driver.find('.test-tools-raw').click();
|
||||
await waitForRawData();
|
||||
}
|
||||
|
||||
async function clickConfirm() {
|
||||
await driver.find('.test-modal-confirm').click();
|
||||
}
|
||||
|
||||
async function clickDuplicateTable() {
|
||||
await driver.find('.test-raw-data-menu-duplicate-table').click();
|
||||
}
|
||||
|
||||
async function clickRemove() {
|
||||
await driver.find('.test-raw-data-menu-remove').click();
|
||||
}
|
||||
|
||||
async function removeRawTable(tableId: string) {
|
||||
await openMenu(tableId);
|
||||
await clickRemove();
|
||||
await clickConfirm();
|
||||
await gu.waitForServer();
|
||||
}
|
||||
|
||||
async function convertToSummary(...groupByColumns: string[]) {
|
||||
// Convert table to a summary table
|
||||
await gu.toggleSidePanel('right', 'open');
|
||||
// Creator Panel > Table
|
||||
await driver.find('.test-right-tab-pagewidget').click();
|
||||
// Tab [Data]
|
||||
await driver.find('.test-config-data').click();
|
||||
// Edit Data Selection
|
||||
await driver.find('.test-pwc-editDataSelection').click();
|
||||
// Σ
|
||||
await driver.find('.test-wselect-pivot').click();
|
||||
// Select Group-By Columns
|
||||
for (const c of groupByColumns) {
|
||||
await driver.findContent('.test-wselect-column', c).click();
|
||||
}
|
||||
// Save
|
||||
await driver.find('.test-wselect-addBtn').click();
|
||||
await gu.waitForServer();
|
||||
}
|
||||
|
||||
async function getRawTableTitles() {
|
||||
return await driver.findAll('.test-raw-data-table-title', e => e.getText());
|
||||
}
|
||||
|
||||
async function getRawTableIds() {
|
||||
return await driver.findAll('.test-raw-data-table-id', e => e.getText());
|
||||
}
|
||||
|
||||
async function getRawTableRows() {
|
||||
return await driver.findAll('.test-raw-data-table-rows', e => e.getText());
|
||||
}
|
||||
|
||||
async function openMenu(tableId: string) {
|
||||
const allTables = await getRawTableIds();
|
||||
const tableIndex = allTables.indexOf(tableId);
|
||||
assert.isTrue(tableIndex >= 0, `No raw table with id ${tableId}`);
|
||||
const menus = await driver.findAll('.test-raw-data-table .test-raw-data-table-menu');
|
||||
assert.equal(menus.length, allTables.length);
|
||||
await menus[tableIndex].click();
|
||||
}
|
||||
|
||||
async function waitForRawData() {
|
||||
await driver.findWait('.test-raw-data-list', 1000);
|
||||
await gu.waitForServer();
|
||||
}
|
||||
|
||||
async function isRemovable(tableId: string){
|
||||
await openMenu(tableId);
|
||||
const disabledItems = await driver.findAll('.test-raw-data-menu-remove.disabled');
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
return disabledItems.length === 0;
|
||||
}
|
||||
|
||||
async function waitForPopup() {
|
||||
assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed());
|
||||
}
|
||||
|
||||
async function assertNoPopup() {
|
||||
assert.isFalse(await driver.find('.test-raw-data-overlay').isPresent());
|
||||
}
|
||||
|
||||
async function waitForAnchorPopup(link: string) {
|
||||
await driver.get(link);
|
||||
await gu.waitForAnchor();
|
||||
await waitForPopup();
|
||||
}
|
Loading…
Reference in New Issue
Block a user