gristlabs_grist-core/test/nbrowser/DescriptionColumn.ts
George Gevoian 0b64e408b0 (core) Fix scrolling and column title bugs
Summary:
On Firefox and Safari, setting scrollLeft to a max safe integer was
causing it to be treated as 0. It's not clear why - for now, the
scrollWidth is used instead.

Also fixes a bug where the column title popup wouldn't appear for a
new column if tab was previously used to close the same popup for
the last column.

Test Plan: Browser test.

Reviewers: JakubSerafin

Reviewed By: JakubSerafin

Subscribers: dsagal

Differential Revision: https://phab.getgrist.com/D3911
2023-06-15 14:51:10 -04:00

564 lines
18 KiB
TypeScript

import {UserAPIImpl} from 'app/common/UserAPI';
import {assert, driver, Key} from 'mocha-webdriver';
import * as gu from 'test/nbrowser/gristUtils';
import {setupTestSuite} from 'test/nbrowser/testUtils';
describe('DescriptionColumn', function() {
this.timeout(20000);
const cleanup = setupTestSuite();
let session: gu.Session;
before(async () => {
session = await gu.session().teamSite.login();
});
it('should switch between close and save', async () => {
await session.tempNewDoc(cleanup);
// Add new column.
await addColumn();
// We should have popup at column D.
await popupIsAt('D');
// Close button should be visible.
assert.isTrue(await closeVisible());
assert.isFalse(await saveVisible());
assert.isFalse(await cancelVisible());
// Change something in the name.
await gu.sendKeys('DD');
// Save button should be visible.
assert.isFalse(await closeVisible());
assert.isTrue(await saveVisible());
assert.isFalse(await saveDisabled());
assert.isTrue(await cancelVisible());
// Restore name.
await gu.sendKeys(Key.BACK_SPACE);
// Close button should be visible.
assert.isTrue(await closeVisible());
assert.isFalse(await saveVisible());
assert.isFalse(await cancelVisible());
// Add description.
await clickAddDescription();
await waitForFocus('description');
// Still close button should be visible.
assert.isTrue(await closeVisible());
assert.isFalse(await saveVisible());
assert.isFalse(await cancelVisible());
// Type something.
await gu.sendKeys('D');
// Save button should be visible.
assert.isFalse(await closeVisible());
assert.isTrue(await saveVisible());
assert.isFalse(await saveDisabled());
assert.isTrue(await cancelVisible());
// Clear and move to label.
await gu.sendKeys(Key.BACK_SPACE);
await gu.sendKeys(Key.ARROW_UP);
await waitForFocus('label');
assert.isTrue(await closeVisible());
assert.isFalse(await saveVisible());
assert.isFalse(await cancelVisible());
// Clear label completely, we have change, but we can't save.
await gu.sendKeys(Key.BACK_SPACE);
assert.isEmpty(await getLabel());
assert.isFalse(await closeVisible());
assert.isTrue(await saveVisible());
// But save button is disabled.
assert.isTrue(await saveDisabled());
assert.isTrue(await cancelVisible());
// Add description.
await gu.sendKeys(Key.ARROW_DOWN);
await waitForFocus('description');
await gu.sendKeys('D');
// Still can't save.
assert.isFalse(await closeVisible());
assert.isTrue(await saveVisible());
assert.isTrue(await saveDisabled());
assert.isTrue(await cancelVisible());
// Clear description completely, restore label and press close.
await gu.sendKeys(Key.BACK_SPACE);
await gu.sendKeys(Key.ARROW_UP);
await waitForFocus('label');
await gu.sendKeys('D');
await pressClose();
// Make sure popup is gone.
assert.isFalse(await popupVisible());
// Make sure column D exists.
assert.isTrue(await gu.getColumnHeader({col: 'D'}).isDisplayed());
await gu.undo();
assert.isFalse(await gu.getColumnHeader({col: 'D'}).isPresent());
});
it('should close popup by enter and escape', async () => {
// Add another column, make sure that enter and escape work.
await addColumn();
await popupIsAt('D');
await gu.sendKeys(Key.ESCAPE);
assert.isFalse(await popupVisible());
// Column D is still there.
assert.isTrue(await gu.getColumnHeader({col: 'D'}).isDisplayed());
await gu.undo();
assert.isFalse(await gu.getColumnHeader({col: 'D'}).isPresent());
await addColumn();
await popupIsAt('D');
await gu.sendKeys(Key.ENTER);
assert.isFalse(await popupVisible());
assert.isTrue(await gu.getColumnHeader({col: 'D'}).isDisplayed());
await gu.undo();
});
it('should show info tooltip in a Grid View', async () => {
await session.tempDoc(cleanup, 'Hello.grist');
await gu.dismissWelcomeTourIfNeeded();
// Start renaming col A.
await doubleClickHeader('A');
await gu.sendKeys('ColumnA');
// Check that description is not visible.
await descriptionIsVisible(false);
await addDescriptionIsVisible(true);
// Press add description.
await clickAddDescription();
// Check that description is visible.
await descriptionIsVisible(true);
await addDescriptionIsVisible(false);
// Wait for focus in the description input
await waitForFocus('description');
// Measure the height of the description input
const rBefore = await driver.find(`.test-column-title-description`).getRect();
// Send some multiline text (with more than three lines to test if it auto grows).
await gu.sendKeys('Line1');
await gu.sendKeys(Key.SHIFT, Key.ENTER, Key.NULL);
await gu.sendKeys('Line2');
await gu.sendKeys(Key.SHIFT, Key.ENTER, Key.NULL);
await gu.sendKeys('Line3');
await gu.sendKeys(Key.SHIFT, Key.ENTER, Key.NULL);
await gu.sendKeys('Line4');
await gu.sendKeys(Key.SHIFT, Key.ENTER, Key.NULL);
// Measure the height of the description input again
const rAfter = await driver.find(`.test-column-title-description`).getRect();
// Make sure it is at least 13 pixel taller (default font height).
assert.isTrue(rAfter.height >= rBefore.height + 13);
// Press save
await pressSave();
// Make sure column is renamed.
let header = await gu.getColumnHeader({col: 'ColumnA'});
// Make sure it has a tooltip.
assert.isTrue(await header.find(".test-column-info-tooltip").isDisplayed());
// Click the tooltip.
await header.find(".test-column-info-tooltip").click();
// Make sure we see the popup.
await waitForTooltip();
// With a proper text.
assert.equal(await driver.find(".test-column-info-tooltip-popup").getText(), 'Line1\nLine2\nLine3\nLine4');
// Undo one (those renames should be bundled).
await gu.undo();
// Make sure column is renamed back.
header = await gu.getColumnHeader({col: 'A'});
// And there is no tooltip.
assert.isFalse(await header.find(".test-column-info-tooltip").isPresent());
});
const saveTest = async (save: () => Promise<void>) => {
const revert = await gu.begin();
// Start renaming col A.
await doubleClickHeader('B');
await gu.sendKeys('ColumnB');
// Press enter.
await save();
await gu.waitForServer();
// Make sure it is renamed.
await gu.getColumnHeader({col: 'ColumnB'});
// Change description by clicking save.
await doubleClickHeader('ColumnB');
await clickAddDescription();
await waitForFocus('description');
await gu.sendKeys('ColumnB description');
await save();
await gu.waitForServer();
// Make sure tooltip is shown.
await clickTooltip('ColumnB');
await gu.waitToPass(async () => {
assert.equal(await driver.findWait(".test-column-info-tooltip-popup", 300).getText(), 'ColumnB description');
});
await gu.sendKeys(Key.ESCAPE);
await revert();
};
it('should support saving by clicking save', async () => {
await saveTest(pressSave);
});
it('should support saving by clicking away', async () => {
await saveTest(() => gu.getCell('E', 5).click());
});
it('should support saving by clicking Ctrl+Enter', async () => {
await saveTest(async () => await gu.sendKeys(Key.chord(await gu.modKey(), Key.ENTER)));
});
it('should support saving by enter', async () => {
const revert = await gu.begin();
// Start renaming col A.
await doubleClickHeader('B');
await gu.sendKeys('ColumnB');
// Make description.
await clickAddDescription();
await gu.sendKeys('ColumnB description');
// Go to label.
await gu.sendKeys(Key.ARROW_UP);
await gu.sendKeys(Key.ARROW_UP);
await waitForFocus('label');
// Save by pressing enter.
await gu.sendKeys(Key.ENTER);
await gu.waitForServer();
// Make sure tooltip is shown.
await clickTooltip('ColumnB');
await gu.waitToPass(async () => {
assert.equal(await driver.findWait(".test-column-info-tooltip-popup", 300).getText(), 'ColumnB description');
});
await gu.sendKeys(Key.ESCAPE);
await revert();
});
it('should support saving by tab', async () => {
await saveTest(() => gu.sendKeys(Key.TAB));
await saveTest(() => gu.sendKeys(Key.SHIFT, Key.TAB, Key.NULL));
});
const cancelTest = async (makeCancel: () => Promise<void>) => {
// Rename column A.
await doubleClickHeader('A');
await gu.sendKeys('ColumnA');
await makeCancel();
await gu.waitForServer();
// Make sure we see column A.
await gu.getColumnHeader({col: 'A'});
// Check the same for description.
await doubleClickHeader('A');
await clickAddDescription();
await gu.sendKeys('ColumnA description');
await makeCancel();
await gu.waitForServer();
// Make sure that there is no tooltip.
assert.isFalse(await gu.getColumnHeader({col: 'A'}).find(".test-column-info-tooltip").isPresent());
};
it('should support canceling by cancel', async () => {
await cancelTest(pressCancel);
});
it('should support canceling by Escape', async () => {
await cancelTest(() => gu.sendKeys(Key.ESCAPE));
});
it('should add description by pressing arrow down', async () => {
await doubleClickHeader('A');
await addDescriptionIsVisible(true);
await descriptionIsVisible(false);
await gu.sendKeys(Key.ARROW_DOWN);
await waitForFocus('description');
await addDescriptionIsVisible(false);
await descriptionIsVisible(true);
// Type something.
await gu.sendKeys('ColumnA description', Key.ENTER);
await gu.sendKeys('ColumnA description');
// Now press 2 times the up key.
await gu.sendKeys(Key.ARROW_UP);
await gu.sendKeys(Key.ARROW_UP);
// We should still be in the description field.
await waitForFocus('description');
// Now press down key and test if that works.
await gu.sendKeys(Key.ARROW_DOWN);
await driver.wait(() => driver.executeScript(() => ((document as any).activeElement.selectionEnd === 39)), 500);
// Now press it 3 times, we should be back in the label field.
await gu.sendKeys(Key.ARROW_UP);
await gu.sendKeys(Key.ARROW_UP);
await gu.sendKeys(Key.ARROW_UP);
// We should be focused back in the label field.
await waitForFocus('label');
await pressCancel();
});
it('should tab to other columns and save', async () => {
const revert = await gu.begin();
// Start renaming col A.
await doubleClickHeader('B');
await gu.sendKeys('ColumnB');
// Press tab.
await gu.sendKeys(Key.TAB);
await gu.waitForServer();
// Make sure it is renamed.
await gu.getColumnHeader({col: 'ColumnB'});
// Make sure we are now at column C.
await popupIsAt('C');
// Rename column C.
await gu.sendKeys('ColumnC');
// Add description.
await driver.find(".test-column-title-add-description").click();
await waitForFocus('description');
// Rename description.
await gu.sendKeys('ColumnC description');
// Go back to column B from description by pressing shift tab
await gu.sendKeys(Key.SHIFT, Key.TAB, Key.NULL);
await gu.waitForServer();
// Make sure we are now at column B.
await popupIsAt('ColumnB');
// Make sure the label has focus.
await waitForFocus('label');
// Go to column C and from the label.
await gu.sendKeys(Key.TAB);
// Make sure we are now at column C.
await popupIsAt('ColumnC');
// Just quick test that shift tab will work.
await gu.sendKeys(Key.SHIFT, Key.TAB, Key.NULL);
// Make sure we are now at column B.
await popupIsAt('ColumnB');
// Go to column C and test if the description was saved.
await gu.sendKeys(Key.TAB);
// Make sure we are now at column C.
await popupIsAt('ColumnC');
// And it has proper description.
assert.equal(await driver.find(".test-column-title-description").getAttribute('value'), 'ColumnC description');
// Close by pressing escape.
await gu.sendKeys(Key.ESCAPE);
await gu.waitForServer();
await revert();
});
it('should reopen editor when adding new column', async () => {
// This partially worked before - there was a bug where if you pressed tab on
// the last column, and then clicked Add Column, the editor wasn't shown, and the
// auto-generated column name was used.
const revert = await gu.begin();
await doubleClickHeader('E');
await gu.sendKeys(Key.TAB);
assert.isFalse(await popupVisible());
await addColumn();
assert.isTrue(await popupVisible());
await gu.sendKeys(Key.ESCAPE);
await gu.waitForServer();
await revert();
});
it('should support basic edition on CardList', async () => {
const mainSession = await gu.session().teamSite.login();
const api = mainSession.createHomeApi();
const doc = await mainSession.tempDoc(cleanup, "CardView.grist", { load: true });
const docId = doc.id;
await addColumnDescription(api, docId, 'B');
// Column description editable in right panel
await driver.find('.test-right-opener').click();
await gu.getCell({ rowNum: 1, col: 'B' }).click();
await driver.find('.test-right-tab-field').click();
assert.equal(await getDescriptionInput().value(), 'This is the column description\nIt is in two lines');
await gu.getCell({ rowNum: 1, col: 'A' }).click();
assert.equal(await getDescriptionInput().value(), '');
// Remove the description
await api.applyUserActions(docId, [
[ 'ModifyColumn', 'Table1', 'B', {
description: ''
} ],
]);
await gu.getCell({ rowNum: 1, col: 'B' }).click();
assert.equal(await getDescriptionInput().value(), '');
});
it('should show info tooltip only if there is a description', async () => {
const mainSession = await gu.session().teamSite.login();
const api = mainSession.createHomeApi();
const doc = await mainSession.tempDoc(cleanup, "CardView.grist", { load: true });
const docId = doc.id;
await addColumnDescription(api, docId, 'B');
await gu.changeWidget('Card');
const detailUndescribedColumnFirstRow = await gu.getDetailCell('A', 1);
assert.isFalse(
await detailUndescribedColumnFirstRow
.findClosest(".g_record_detail_el")
.find(".test-column-info-tooltip")
.isPresent()
);
const detailDescribedColumnFirstRow = await gu.getDetailCell('B', 1);
const toggle = await detailDescribedColumnFirstRow
.findClosest(".g_record_detail_el")
.find(".test-column-info-tooltip");
// The toggle to show the description is present if there is a description
assert.isTrue(await toggle.isPresent());
// Open the tooltip
await toggle.click();
await waitForTooltip();
// Check the content of the tooltip
const descriptionTooltip = await driver
.find('.test-column-info-tooltip-popup');
assert.equal(await descriptionTooltip.getText(), 'This is the column description\nIt is in two lines');
});
});
async function clickTooltip(col: string) {
await gu.getColumnHeader({col}).find(".test-column-info-tooltip").click();
}
async function addDescriptionIsVisible(visible = true) {
if (visible) {
assert.isTrue(await driver.find(".test-column-title-add-description").isDisplayed());
} else {
assert.isFalse(await driver.find(".test-column-title-add-description").isPresent());
}
}
async function descriptionIsVisible(visible = true) {
if (visible) {
assert.isTrue(await driver.find(".test-column-title-description").isDisplayed());
} else {
assert.isFalse(await driver.find(".test-column-title-description").isPresent());
}
}
async function addColumnDescription(api: UserAPIImpl, docId: string, columnName: string) {
await api.applyUserActions(docId, [
[ 'ModifyColumn', 'Table1', columnName, {
description: 'This is the column description\nIt is in two lines'
} ],
]);
}
function getDescriptionInput() {
return driver.find('.test-right-panel .test-column-description');
}
function getLabel() {
return driver.findWait(".test-column-title-label", 1000).getAttribute('value');
}
async function popupVisible() {
if (await driver.find(".test-column-title-popup").isPresent()) {
return await driver.find(".test-column-title-popup").isDisplayed();
} else {
return false;
}
}
async function popupIsAt(col: string) {
// Make sure we are now at column.
assert.equal(await getLabel(), col);
// Make sure that popup is near the column.
const headerCRect = await gu.getColumnHeader({col}).getRect();
const popup = await driver.find(".test-column-title-popup").getRect();
assert.isAtLeast(popup.x, headerCRect.x - 2);
assert.isBelow(popup.x, headerCRect.x + 2);
assert.isAtLeast(popup.y, headerCRect.y + headerCRect.height - 2);
assert.isBelow(popup.y, headerCRect.y + headerCRect.height + 2);
}
async function doubleClickHeader(col: string) {
const header = await gu.getColumnHeader({col});
await header.click();
await header.click();
await waitForFocus('label');
}
async function waitForFocus(field: 'label'|'description') {
await gu.waitToPass(async () => assert.isTrue(await driver.find(`.test-column-title-${field}`).hasFocus()), 200);
}
async function waitForTooltip() {
await gu.waitToPass(async () => {
assert.isTrue(await driver.find(".test-column-info-tooltip-popup").isDisplayed());
});
}
async function pressSave() {
await driver.find(".test-column-title-save").click();
await gu.waitForServer();
}
async function pressClose() {
await driver.find(".test-column-title-close").click();
await gu.waitForServer();
}
async function saveDisabled() {
const value = await driver.find(".test-column-title-save").getAttribute('disabled');
return value === 'true';
}
async function pressCancel() {
await driver.find(".test-column-title-cancel").click();
await gu.waitForServer();
}
async function clickAddDescription() {
await driver.find(".test-column-title-add-description").click();
await waitForFocus('description');
}
async function addColumn() {
await driver.find(".mod-add-column").click();
await gu.waitForServer();
}
async function closeVisible() {
return await driver.find(".test-column-title-close").isDisplayed();
}
async function saveVisible() {
return await driver.find(".test-column-title-save").isDisplayed();
}
async function cancelVisible() {
return await driver.find(".test-column-title-cancel").isDisplayed();
}