import * as gu from 'test/nbrowser/gristUtils'; import {setupTestSuite} from 'test/nbrowser/testUtils'; import {getCollapsedSection, openCollapsedSectionMenu} from 'test/nbrowser/ViewLayoutUtils'; import {assert, driver, Key, WebElement, WebElementPromise} from 'mocha-webdriver'; import {arrayRepeat} from 'app/plugin/gutil'; import {addStatic, serveSomething} from 'test/server/customUtil'; import {AccessLevel} from 'app/common/CustomWidget'; const GAP = 16; // Distance between buttons representing collapsed widgets. describe("ViewLayoutCollapse", function() { this.timeout('50s'); const cleanup = setupTestSuite(); gu.bigScreen(); let session: gu.Session; before(async () => { session = await gu.session().login(); await session.tempNewDoc(cleanup); }); it('fix:copies collapsed sections properly', async function() { // When one of 2 widget was collapsed, the resulting widget can become a root section. Then, // when a page was duplicated, the layout was duplicated incorrectly (with wrong collapsed // section). This resulted in a bug, when the root section was deleted, as it was the last // section in the saved layout, but not the last section on the visible layout. // Add new page with new table. await gu.addNewPage('Table', 'New Table', { tableName: 'Broken' }); await gu.renameActiveSection('Collapsed'); // Add section here (with the same table). await gu.addNewSection('Table', 'Broken'); // Rename it so that it is easier to find. await gu.renameActiveSection('NotCollapsed'); // Now store the layout, by amending it (so move the collapsed widget below). const {height} = await gu.getSection('NotCollapsed').getRect(); await dragMain('Collapsed'); await move(gu.getSection('NotCollapsed'), { x: 50, y: height / 2 }); await driver.sleep(300); await move(gu.getSection('NotCollapsed'), { x: 100, y: height / 2 }); await driver.sleep(300); await driver.withActions(actions => actions.release()); // Wait for the debounced save. await driver.sleep(1500); await gu.waitForServer(); // Now collapse it. await collapseByMenu('Collapsed'); // Now duplicate the page. await gu.duplicatePage('Broken', 'Broken2'); // Now on this page we saw two uncollapsed sections (make sure this is not the case). assert.deepEqual(await gu.getSectionTitles(), ['NotCollapsed']); }); it('fix:can delete root section', async function() { // But even if the layout spec was corrupted, we still should be able to delete the root section // when replacing it with new one. // Break the spec. const specJson: string = await driver.executeScript( 'return gristDocPageModel.gristDoc.get().docModel.views.rowModels[3].layoutSpec()' ); // To break the spec, we will replace id of the collapsed section, then viewLayout will try to fix it, // by rendering the missing section without patching the layout spec (which is good, because this could // happen on readonly doc or a snapshot). const spec = JSON.parse(specJson); spec.collapsed[0].leaf = -10; await driver.executeScript( `gristDocPageModel.gristDoc.get().docModel.views.rowModels[3].layoutSpec.setAndSave('${JSON.stringify(spec)}')` ); await gu.waitForServer(); // We now should see two sections. assert.deepEqual(await gu.getSectionTitles(), ['NotCollapsed', 'Collapsed']); // And we should be able to delete the top one (NotCollapsed). await gu.openSectionMenu('viewLayout', 'NotCollapsed'); await driver.findContent('.test-cmd-name', 'Delete widget').click(); await gu.waitForServer(); await gu.checkForErrors(); }); it('fix: custom widget should restart when added back after collapsing', async function() { await session.tempDoc(cleanup, 'Investment Research.grist'); await gu.openPage("Overview"); const revert = await gu.begin(); // Add custom section. await gu.addNewPage('Table', 'Companies'); await gu.addNewSection('Custom', 'Companies', { selectBy: 'COMPANIES'}); // Serve custom widget. const widgetServer = await serveSomething(app => { addStatic(app); }); cleanup.addAfterAll(widgetServer.shutdown); await gu.openWidgetPanel(); await gu.setWidgetUrl(widgetServer.url + '/probe/index.html'); await gu.widgetAccess(AccessLevel.full); // Collapse it. await collapseByMenu('COMPANIES Custom'); // Now restore its position. await addToMainByMenu('COMPANIES Custom'); // Collapsed widget used to lost connection with Grist as it was disposed to early. // Make sure that this widget can call the API. await gu.doInIframe(async () => { await gu.waitToPass(async () => { assert.equal(await driver.find('#output').getText(), `["Companies","Investments","Companies_summary_category_code","Investments_summary_funded_year",` + `"Investments_summary_Company_category_code_funded_year","Investments_summary_Company_category_code"]` ); }); }); // Make sure we don't have an error. await gu.checkForErrors(); await revert(); }); it('fix: custom widget should not throw errors when collapsed', async function() { const revert = await gu.begin(); // Add custom section. await gu.addNewPage('Table', 'Companies'); await gu.addNewSection('Custom', 'Companies', { selectBy: 'COMPANIES'}); // Serve custom widget. const widgetServer = await serveSomething(app => { addStatic(app); }); cleanup.addAfterAll(widgetServer.shutdown); await gu.openWidgetPanel(); await gu.setWidgetUrl(widgetServer.url + '/probe/index.html'); await gu.widgetAccess(AccessLevel.full); // Collapse it. await collapseByMenu('COMPANIES Custom'); // Change cursor position in the active section. await gu.getCell(2, 4).click(); // Put custom section in popup. await openCollapsedSection('COMPANIES Custom'); // Close it by pressing escape. await gu.sendKeys(Key.ESCAPE); // Change cursor once again. await gu.getCell(2, 5).click(); // Make sure we don't have an error (there was a bug here). await gu.checkForErrors(); await revert(); }); it('fix: should resize other sections correctly when maximized and linked', async function() { const revert = await gu.begin(); // If there are two sections linked, but one is collapsed, and user is changing the row // in the popup of the maximized section, the other section should resize correctly. // Add two linked tables. await gu.addNewTable('Table1'); await gu.addNewSection('Table', 'New Table'); await gu.toggleSidePanel('right', 'open'); // Set A in Table2 to be linked in Table1, by ref column await gu.openColumnMenu('A', 'Column Options'); // Change it to the Ref column of TABLE1 await gu.setType('Reference'); await gu.setRefTable('Table1'); await gu.setRefShowColumn('A'); // Select it by Table1. await gu.selectBy('TABLE1'); // Now add 2 records with 'White' and 'Black' in Table1 await gu.sendActions([ ['BulkAddRecord', 'Table1', arrayRepeat(2, null), { A: ['White', 'Black'] }], // And 30 records in Table2 that are connected to White. ['BulkAddRecord', 'Table2', arrayRepeat(30, null), { A: arrayRepeat(30, 1) }], // And 30 records in Table2 that are connected to Black. ['BulkAddRecord', 'Table2', arrayRepeat(30, null), { A: arrayRepeat(30, 2) }], ]); // Now select White in Table1. await gu.getCell('A', 1, 'Table1').click(); // Now expand Table1. await gu.expandSection(); // Change to black. await gu.getCell('A', 2, 'Table1').click(); // Close popup by sending ESCAPE await gu.sendKeys(Key.ESCAPE); // Make sure we see 30 records in Table2. const count = await driver.executeScript(` const section = Array.from(document.querySelectorAll('.test-widget-title-text')) .find(e => e.textContent === 'TABLE2') .closest('.viewsection_content'); return Array.from(section.querySelectorAll('.gridview_data_row_num')).length; `); assert.equal(count, 30 + 1); await revert(); }); it('fix: should support searching', async function() { // Collapse Companies section (a first one). await collapseByMenu(COMPANIES); // Clear any saved position state. await driver.executeScript('window.sessionStorage.clear(); window.localStorage.clear();'); // Refresh. await driver.navigate().refresh(); await gu.waitForDocToLoad(); // Here we had a bug that the hidden section was active, and the search was not working as it was // starting with the hidden section. // Now search (for something in the INVESTMENTS section) await gu.search('2006'); await gu.closeSearch(); // Make sure we don't have an error. await gu.checkForErrors(); assert.equal(await gu.getActiveSectionTitle(), INVESTMENTS); // Make sure we are in 1column 9th row. assert.deepEqual(await gu.getCursorPosition(), {rowNum: 9, col: 0}); // Hide companies chart, and search for mobile (should show no results). await collapseByMenu(COMPANIES_CHART); await gu.search('mobile'); await gu.hasNoResult(); await gu.closeSearch(); // Open companies in the popup. await openCollapsedSection(COMPANIES); // Search for 2006, there will be no results. await gu.search('2006'); await gu.hasNoResult(); // Now search for web. await gu.closeSearch(); await gu.search('web'); assert.deepEqual(await gu.getCursorPosition(), {rowNum: 5, col: 0}); // Recreate document (can't undo). await session.tempDoc(cleanup, 'Investment Research.grist'); }); it('fix: should not dispose the instance when drag is cancelled', async function() { const revert = await gu.begin(); // Collapse a section. await collapseByMenu(INVESTMENTS); // Drag it and then cancel. await dragCollapsed(INVESTMENTS); const logo = driver.find('.test-dm-logo'); await move(logo, {y: 0}); await move(logo, {y: -1}); // Drop it here. await driver.withActions(actions => actions.release()); // Now open it in the full view. await openCollapsedSection(INVESTMENTS); // And make sure we can move cursor. await gu.getCell(1, 1).click(); assert.deepEqual(await gu.getCursorPosition(), {rowNum: 1, col: 1}); await gu.getCell(1, 2).click(); assert.deepEqual(await gu.getCursorPosition(), {rowNum: 2, col: 1}); // Change its type, and check that it works. await gu.changeWidget('Card List'); // Undo it. await gu.undo(); await gu.getCell(1, 3).click(); assert.deepEqual(await gu.getCursorPosition(), {rowNum: 3, col: 1}); await gu.sendKeys(Key.ESCAPE); // Move it back await dragCollapsed(INVESTMENTS); // Move back and drop. await gu.getSection(COMPANIES_CHART).getRect(); await move(getDragElement(COMPANIES_CHART)); await driver.sleep(100); await move(getDragElement(COMPANIES_CHART), {x : 200}); await driver.sleep(300); assert.lengthOf(await driver.findAll(".layout_editor_drop_target.layout_hover"), 1); await driver.withActions(actions => actions.release()); await driver.sleep(600); // And make sure we can move cursor. await gu.getCell(1, 1).click(); assert.deepEqual(await gu.getCursorPosition(), {rowNum: 1, col: 1}); await gu.getCell(1, 2).click(); assert.deepEqual(await gu.getCursorPosition(), {rowNum: 2, col: 1}); await waitForSave(); await revert(); }); it('fix: should work when the page is refreshed', async function() { const revert = await gu.begin(); await gu.openPage("Companies"); await gu.selectSectionByTitle("Companies"); // Go to second row. await gu.getCell(0, 2).click(); // Make sure we see correct company card. assert.equal(await gu.getCardCell('name', 'COMPANIES Card').getText(), '#NAME?'); // Hide first section. await collapseByMenu("Companies"); await waitForSave(); // Refresh the page. await driver.navigate().refresh(); await gu.waitForDocToLoad(); // Make sure card is still at the correct row. await gu.waitToPass(async () => { assert.equal(await gu.getCardCell('name', 'COMPANIES Card').getText(), '#NAME?'); }); await addToMainByMenu("Companies"); await revert(); }); it('fix: should support anchor links', async function() { const revert = await gu.begin(); // Open 42Floors in Companies section. assert.equal(await gu.getActiveSectionTitle(), "COMPANIES"); await gu.getCell('Link', 11).click(); assert.equal(await gu.getActiveCell().getText(), '42Floors'); // Open 12 row (Alex Bresler, angel). await gu.getCell('funding_round_type', 12, 'Investments').click(); assert.equal(await gu.getActiveCell().getText(), 'angel'); // Copy anchor link. const link = await gu.getAnchor(); // Collapse first section. await collapseByMenu("COMPANIES"); // Clear any saved position state. await driver.executeScript('window.sessionStorage.clear(); window.localStorage.clear();'); // Navigate to the home screen. await gu.loadDocMenu('/o/docs'); // Now go to the anchor. await driver.get(link); await gu.waitForAnchor(); const cursor = await gu.getCursorPosition(); assert.equal(cursor.rowNum, 12); assert.equal(cursor.col, 1); assert.equal(await gu.getActiveCell().getText(), 'angel'); assert.equal(await gu.getActiveSectionTitle(), 'INVESTMENTS'); assert.match(await driver.getCurrentUrl(), /\/o\/docs\/[^/]+\/Investment-Research\/p\/1$/); await revert(); }); it("should not autoexpand the tray on a page with a single widget", async () => { await gu.openPage("Investments"); assert.equal((await driver.findAll(".viewsection_content")).length, 1); // Start drag the main section. await dragMain("INVESTMENTS"); // Move it over the logo, so that the tray thinks that it should expand. const logo = driver.find('.test-dm-logo'); await move(logo, {y: 0}); await move(logo, {y: -1}); await driver.sleep(100); // Make sure the tray was not tricked into expanding itself. assert.isFalse(await layoutTray().isDisplayed()); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-empty-box"), 0); // No empty box // Drop it on the button, it should go back to where it was. await driver.withActions(actions => actions.release()); }); it("should autoexpand the tray", async () => { await gu.openPage("Overview"); // Get one of the sections and start dragging it. await dragMain(COMPANIES_CHART); // The tray should not be expanded. assert.isFalse(await layoutTray().isDisplayed()); const logo = driver.find('.test-dm-logo'); // Now move it to the top, so that tray should be expanded. await move(logo, {y: 0}); await driver.sleep(100); // Now the tray is visible assert.isTrue(await layoutTray().isDisplayed()); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-empty-box"), 1); // One empty box assert.isTrue(await layoutEditor().matches('[class*=-is-active]')); // Is active assert.isFalse(await layoutEditor().matches('[class*=-is-target]')); // Is not a target // Drop it on the button, it should go back to where it was. await driver.withActions(actions => actions.release()); // The tray should not be expanded. assert.isFalse(await layoutTray().isDisplayed()); await gu.checkForErrors(); }); it("should drag onto main area", async () => { const revert = await gu.begin(); await collapseByMenu(COMPANIES); await collapseByMenu(INVESTMENTS); await dragCollapsed(COMPANIES); const chartCords = await gu.getSection(COMPANIES_CHART).getRect(); await move(getDragElement(COMPANIES_CHART)); await driver.sleep(100); await move(getDragElement(COMPANIES_CHART), {x : 10}); await driver.sleep(300); // We should have a drop target. const dropTarget = await driver.find(".layout_editor_drop_target.layout_hover"); const dCords = await dropTarget.getRect(); // It should be more or less on the left of the chart. assertDistance(dCords.x, chartCords.x, 20); assertDistance(dCords.y, chartCords.y, 20); // Move away from the drop target. const addButton = driver.find('.test-dp-add-new'); await move(addButton); await driver.sleep(300); // Drop target should be gone. assert.lengthOf(await driver.findAll(".layout_editor_drop_target.layout_hover"), 0); // Move back and drop. await move(getDragElement(COMPANIES_CHART)); await driver.sleep(100); // Split the movement into two parts, to make sure layout sees the mouse move. await move(getDragElement(COMPANIES_CHART), {x : 10}); await driver.sleep(200); assert.lengthOf(await driver.findAll(".layout_editor_drop_target.layout_hover"), 1); await driver.withActions(actions => actions.release()); await driver.sleep(600); // This animation can be longer. // Make sure it was dropped. assert.lengthOf(await layoutTray().findAll(".test-layoutTray-leaf-box"), 1); // Only one collapsed box. assert.lengthOf(await layoutTray().findAll(".test-layoutTray-empty-box"), 0); // No empty box. assert.lengthOf(await layoutTray().findAll(".test-layoutTray-target-box"), 0); // No target box. assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS]); // Only investments is collapsed. assert.deepEqual(await mainSectionTitles(), [COMPANIES, COMPANIES_CHART, INVESTMENTS_CHART]); // Check that it was dropped on the left top side. const companiesCords = await gu.getSection(COMPANIES).getRect(); assertDistance(companiesCords.x, chartCords.x, 20); assertDistance(companiesCords.y, chartCords.y, 20); // It should be half as tall as the main layout. const root = await driver.find(".layout_root").getRect(); assertDistance(companiesCords.height, root.height / 2, 30); // And almost half as wide. assertDistance(companiesCords.width, root.width / 2, 30); // Now move it back to the tray. But first collapse another section (so we can test inbetween target). await collapseByMenu(COMPANIES_CHART); await dragMain(COMPANIES); // Try to move it as the first element. const firstLeafSize = await firstLeaf().getRect(); await move(firstLeaf(), { x: -firstLeafSize.width / 2 }); await driver.sleep(300); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-target-box"), 1); // Make sure that the target is in right place. let target = await layoutTray().find(".test-layoutTray-target-box").getRect(); assertDistance(target.x, firstLeafSize.x, 10); // Now as the second element. await move(firstLeaf(), { x: firstLeafSize.width / 2 + GAP }); await driver.sleep(300); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-target-box"), 1); target = await layoutTray().find(".test-layoutTray-target-box").getRect(); assertDistance(target.x, firstLeafSize.x + firstLeafSize.width + GAP, 10); // Move away to make sure the target is gone. await move(addButton); await driver.sleep(300); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-target-box"), 0); // Move back and drop. await move(firstLeaf(), { x: firstLeafSize.width / 2 + GAP }); await driver.sleep(300); await driver.withActions(actions => actions.release()); await driver.sleep(600); // Make sure it was dropped. assert.lengthOf(await layoutTray().findAll(".test-layoutTray-leaf-box"), 3); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-empty-box"), 0); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-target-box"), 0); assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS, COMPANIES, COMPANIES_CHART]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS_CHART]); await waitForSave(); // Layout save is debounced 1s. // Test couple of undo steps. await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS, COMPANIES_CHART]); assert.deepEqual(await mainSectionTitles(), [COMPANIES, INVESTMENTS_CHART]); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS]); assert.deepEqual(await mainSectionTitles(), [COMPANIES, COMPANIES_CHART, INVESTMENTS_CHART]); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES, INVESTMENTS]); assert.deepEqual(await mainSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART]); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES]); assert.deepEqual(await mainSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART, INVESTMENTS]); await gu.redo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES, INVESTMENTS]); assert.deepEqual(await mainSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART]); await revert(); assert.deepEqual(await collapsedSectionTitles(), []); assert.deepEqual(await mainSectionTitles(), [COMPANIES_CHART, COMPANIES, INVESTMENTS_CHART, INVESTMENTS]); await gu.checkForErrors(); }); it("should reorder collapsed sections", async () => { const revert = await gu.begin(); await collapseByMenu(COMPANIES); await collapseByMenu(INVESTMENTS); await collapseByMenu(COMPANIES_CHART); await dragCollapsed(COMPANIES); // We should see the empty box in the layout. assert.lengthOf(await layoutTray().findAll(".test-layoutTray-empty-box"), 1); // The section is actually removed from the layout tray. assert.lengthOf(await layoutTray().findAll(".test-layoutTray-leaf-box"), 2); assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS, COMPANIES_CHART]); // Layout should be active and accepting. assert.isTrue(await layoutEditor().matches('[class*=-is-active]')); assert.isTrue(await layoutEditor().matches('[class*=-is-target]')); // Move mouse somewhere else, layout should not by highlighted. const addButton = driver.find('.test-dp-add-new'); await move(addButton); assert.isTrue(await layoutEditor().matches('[class*=-is-active]')); assert.isFalse(await layoutEditor().matches('[class*=-is-target]')); // Move to the first leaf, and wait for the target to show up. const first = await firstLeaf().getRect(); await move(firstLeaf(), {x : -first.width / 2}); await driver.sleep(300); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-target-box"), 1); // Make sure that the target is in right place. let target = await layoutTray().find(".test-layoutTray-target-box").getRect(); assert.isBelow(Math.abs(target.x - first.x), 10); assert.isBelow(Math.abs(target.y - first.y), 10); assert.isBelow(Math.abs(target.height - first.height), 10); // Move away and make sure the target is gone. await move(addButton); await driver.sleep(300); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-target-box"), 0); // Move between first and second leaf. await move(firstLeaf(), {x : first.width / 2 + GAP}); await driver.sleep(300); assert.lengthOf(await layoutTray().findAll(".test-layoutTray-target-box"), 1); target = await layoutTray().find(".test-layoutTray-target-box").getRect(); assert.isBelow(Math.abs(target.height - first.height), 2); assert.isBelow(Math.abs(target.y - first.y), 2); // Should be between first and second leaf. assert.isBelow(Math.abs(target.x - (first.x + first.width + GAP)), 10); // Drop here. await driver.withActions(actions => actions.release()); await waitForSave(); // Wait for layout to be saved. // Target is gone. assert.lengthOf(await layoutTray().findAll(".test-layoutTray-empty-box"), 0); // And we have 3 sections in the layout. assert.lengthOf(await layoutTray().findAll(".test-layoutTray-leaf-box"), 3); assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS, COMPANIES, COMPANIES_CHART]); // Undo. await gu.undo(); // Order should be restored. assert.deepEqual(await collapsedSectionTitles(), [COMPANIES, INVESTMENTS, COMPANIES_CHART]); await revert(); await gu.checkForErrors(); }); it("should collapse sections and expand using menu", async () => { await collapseByMenu(COMPANIES_CHART); await gu.checkForErrors(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES_CHART]); // Make sure that other sections are not collapsed. assert.deepEqual(await mainSectionTitles(), [COMPANIES, INVESTMENTS_CHART, INVESTMENTS]); await collapseByMenu(INVESTMENTS_CHART); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART]); assert.deepEqual(await mainSectionTitles(), [COMPANIES, INVESTMENTS]); await collapseByMenu(COMPANIES); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART, COMPANIES]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS]); // The last section is INVESTMENTS, which can't be collapsed. await gu.openSectionMenu('viewLayout', INVESTMENTS); assert.equal(await driver.find('.test-section-collapse').matches('[class*=disabled]'), true); await driver.sendKeys(Key.ESCAPE); // Now expand them one by one and test. await addToMainByMenu(COMPANIES_CHART); await gu.checkForErrors(); assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS_CHART, COMPANIES]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS, COMPANIES_CHART]); await addToMainByMenu(INVESTMENTS_CHART); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS, COMPANIES_CHART, INVESTMENTS_CHART]); await gu.checkForErrors(); await addToMainByMenu(COMPANIES); assert.deepEqual(await collapsedSectionTitles(), []); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS, COMPANIES_CHART, INVESTMENTS_CHART, COMPANIES]); await gu.checkForErrors(); // Now revert everything using undo but test each step. await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS, COMPANIES_CHART, INVESTMENTS_CHART]); await gu.checkForErrors(); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS_CHART, COMPANIES]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS, COMPANIES_CHART]); await gu.checkForErrors(); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART, COMPANIES]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS]); await gu.checkForErrors(); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART]); assert.deepEqual(await mainSectionTitles(), [COMPANIES, INVESTMENTS]); await gu.checkForErrors(); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES_CHART]); assert.deepEqual(await mainSectionTitles(), [COMPANIES, INVESTMENTS_CHART, INVESTMENTS]); await gu.checkForErrors(); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), []); assert.deepEqual(await mainSectionTitles(), [COMPANIES_CHART, COMPANIES, INVESTMENTS_CHART, INVESTMENTS]); await gu.checkForErrors(); }); it("should remove sections from collapsed tray", async () => { const revert = await gu.begin(); // Collapse everything we can. await collapseByMenu(COMPANIES_CHART); await collapseByMenu(INVESTMENTS_CHART); await collapseByMenu(COMPANIES); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS]); // Now remove them using menu. await removeMiniSection(COMPANIES_CHART); await gu.checkForErrors(); // Check that the section is removed from the collapsed tray. assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS_CHART, COMPANIES]); // Make sure it is stays removed when we move to the other page. await gu.openPage("Investments"); // Go back. await gu.openPage("Overview"); await gu.checkForErrors(); // Test if we see everything as it was. assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS_CHART, COMPANIES]); // Make sure that visible sections are not affected. assert.deepEqual(await mainSectionTitles(), [INVESTMENTS]); // Remove the other sections. await removeMiniSection(INVESTMENTS_CHART); await removeMiniSection(COMPANIES); assert.deepEqual(await collapsedSectionTitles(), []); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS]); // Now revert everything using undo but test each step. await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS]); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [INVESTMENTS_CHART, COMPANIES]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS]); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART, COMPANIES]); assert.deepEqual(await mainSectionTitles(), [INVESTMENTS]); await gu.undo(); assert.deepEqual(await collapsedSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART]); assert.deepEqual(await mainSectionTitles(), [COMPANIES, INVESTMENTS]); // Ok, we are good, revert back to the original state. await revert(); await gu.checkForErrors(); }); it("should switch active section when collapsed", async () => { const revert = await gu.begin(); await gu.selectSectionByTitle(gu.exactMatch(COMPANIES)); // Make sure we are active. assert.equal(await gu.getActiveSectionTitle(), COMPANIES); // Collapse it. await collapseByMenu(COMPANIES); // Make sure it is collapsed. assert.deepEqual(await collapsedSectionTitles(), [COMPANIES]); // Make sure that now COMPANIES_CHART is active. (first one). assert.equal(await gu.getActiveSectionTitle(), COMPANIES_CHART); // Expand COMPANIES. await addToMainByMenu(COMPANIES); // Make sure that now it is active. assert.equal(await gu.getActiveSectionTitle(), COMPANIES); await revert(); await gu.checkForErrors(); }); it("should show section on popup when clicked", async () => { const revert = await gu.begin(); await collapseByMenu(COMPANIES); await openCollapsedSection(COMPANIES); // Make sure it is expanded. assert.isTrue(await driver.find(".test-viewLayout-overlay").matches("[class*=-active]")); assert.equal(await gu.getActiveSectionTitle(), COMPANIES); // Make sure that the panel shows it. await gu.toggleSidePanel('right', 'open'); await driver.find('.test-config-widget').click(); assert.equal(await driver.find('.test-right-widget-title').value(), COMPANIES); // Make sure we see proper items in the menu. await gu.openSectionMenu('viewLayout', COMPANIES); // Collapse widget menu item is disabled. assert.equal(await driver.find('.test-section-collapse').matches('[class*=disabled]'), true); // Delete widget is enabled. assert.equal(await driver.find('.test-section-delete').matches('[class*=disabled]'), false); await driver.sendKeys(Key.ESCAPE); // Expand button is not visible assert.lengthOf(await driver.findAll(".active_section .test-section-menu-expandSection"), 0); // We can rename a section using the popup. await gu.renameActiveSection("New name"); assert.equal(await gu.getActiveSectionTitle(), "New name"); // Make sure the name is reflected in the collapsed tray. await gu.sendKeys(Key.ESCAPE); assert.deepEqual(await collapsedSectionTitles(), ["New name"]); // Open it back. await openCollapsedSection("New name"); // Rename it back using undo. await gu.undo(); assert.equal(await gu.getActiveSectionTitle(), COMPANIES); // Now remove it. await gu.openSectionMenu('viewLayout', COMPANIES); await driver.find('.test-section-delete').click(); await gu.waitForServer(); // Make sure it is closed. assert.isFalse(await driver.find(".test-viewLayout-overlay").matches("[class*=-active]")); // Make sure it is removed. assert.deepEqual(await collapsedSectionTitles(), []); // Make sure it didn't reappear on the main area. assert.deepEqual(await mainSectionTitles(), [COMPANIES_CHART, INVESTMENTS_CHART, INVESTMENTS]); await revert(); await gu.checkForErrors(); }); it("should collapse and expand charts without an error", async () => { const revert = await gu.begin(); await collapseByMenu(INVESTMENTS); await dragMain(COMPANIES_CHART); const firstRect = await firstLeaf().getRect(); await move(firstLeaf(), { x: firstRect.width / 2 + GAP }); await driver.sleep(300); await driver.withActions(actions => actions.release()); await waitForSave(); // Resize is delayed. await gu.checkForErrors(); await revert(); }); it("should drop on the empty space", async () => { const revert = await gu.begin(); // Get one of the sections and start dragging it. await dragMain(COMPANIES_CHART); // Move it over the logo to show the tray. const logo = driver.find('.test-dm-logo'); await move(logo, {y: 0}); await move(logo, {y: -20}); await driver.sleep(100); // Now the tray is visible assert.isTrue(await layoutTray().isDisplayed()); // Move it on the empty space just after the empty box const emptyBox = await layoutTray().find(".test-layoutTray-empty-box"); const emptyBoxCords = await emptyBox.getRect(); await move(emptyBox, {x: emptyBoxCords.width + 100 }); // Make sure that the empty box is not active. assert.isFalse(await emptyBox.matches('[class*=-is-active]')); // Drop it here await driver.withActions(actions => actions.release()); await driver.sleep(600); // Wait for animation to finish. await waitForSave(); // The tray should stay expanded. assert.isTrue(await layoutTray().isDisplayed()); // Check that the section was collapsed. assert.deepEqual(await collapsedSectionTitles(), [COMPANIES_CHART]); // And other sections are still there. assert.deepEqual(await mainSectionTitles(), [COMPANIES, INVESTMENTS_CHART, INVESTMENTS]); await gu.checkForErrors(); await revert(); await gu.checkForErrors(); }); it("should clear layout when dropped section is removed", async () => { await session.tempNewDoc(cleanup, 'CollapsedBug.grist'); // Add a new section based on current table. await gu.addNewSection('Table', 'Table1'); // It will have id 3 (1 is raw, 2 is visible). // Collapse it. await gu.renameActiveSection('ToDelete'); await collapseByMenu('ToDelete'); // Remove it from the tray. await openCollapsedSectionMenu('ToDelete'); await driver.find('.test-section-delete').click(); await gu.waitForServer(); await waitForSave(); // Now add another one, it will have the same id (3) and it used to be collapsed when added // as the layout was not cleared. await gu.addNewSection('Table', 'Table1'); // Make sure it is expanded. assert.deepEqual(await mainSectionTitles(), ['TABLE1', 'TABLE1']); assert.deepEqual(await collapsedSectionTitles(), []); }); }); async function addToMainByMenu(section: string) { await openCollapsedSectionMenu(section); await driver.find('.test-section-expand').click(); await gu.waitForServer(); await gu.checkForErrors(); } async function dragCollapsed(section: string) { const handle = getCollapsedSection(section).find('.draggable-handle'); await driver.withActions((actions) => actions .move({origin: handle}) .press()); await move(handle, {x : 10, y: 10}); return handle; } async function dragMain(section: string) { const handle = gu.getSection(section).find('.viewsection_drag_indicator'); await driver.withActions((actions) => actions .move({origin: handle})); await driver.withActions((actions) => actions .move({origin: handle, x : 1}) // This is needed to show the drag element. .press()); await move(handle, {x : 10, y: 10}); return handle; } async function openCollapsedSection(section: string) { await getCollapsedSection(section).find('.draggable-handle').click(); } async function removeMiniSection(section: string) { await openCollapsedSectionMenu(section); await driver.find('.test-section-delete').click(); await gu.waitForServer(); await gu.checkForErrors(); } async function collapseByMenu(section: string) { await gu.openSectionMenu('viewLayout', section); await driver.find('.test-section-collapse').click(); await gu.waitForServer(); await gu.checkForErrors(); } // Returns the titles of all collapsed sections. async function collapsedSectionTitles() { return await layoutTray().findAll('.test-layoutTray-leaf-box .test-collapsed-section-title', e => e.getText()); } // Returns titles of all sections in the view layout. async function mainSectionTitles() { return await driver.findAll('.layout_root .test-viewsection-title', e => e.getText()); } async function move(element: WebElementPromise|WebElement, offset: {x?: number, y?: number} = {x: 0, y: 0}) { // With current version of webdriver, a fractional values will get ignored, so round to nearest. if (offset.x) { offset.x = Math.round(offset.x); } if (offset.y) { offset.y = Math.round(offset.y); } await driver.withActions(actions => actions.move({origin: element, ...offset})); } function getDragElement(section: string) { return gu.getSection(section).find('.viewsection_drag_indicator'); } function layoutTray() { return driver.find(".test-layoutTray-layout"); } function firstLeaf() { return layoutTray().find(".test-layoutTray-leaf-box"); } function layoutEditor() { return driver.find(".test-layoutTray-editor"); } const COMPANIES_CHART = 'COMPANIES [by category_code] Chart'; const INVESTMENTS_CHART = 'INVESTMENTS [by funded_year] Chart'; const COMPANIES = 'COMPANIES [by category_code]'; const INVESTMENTS = 'INVESTMENTS [by funded_year]'; function assertDistance(left: number, right: number, max: number) { return assert.isBelow(Math.abs(left - right), max); } async function waitForSave() { await gu.waitToPass(async () => { const pending = await driver.findAll(".test-viewLayout-save-pending"); assert.isTrue(pending.length === 0); await gu.waitForServer(); }, 3000); }