mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
0b64e408b0
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
564 lines
18 KiB
TypeScript
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();
|
|
}
|