(core) Adding links to description tooltips

Summary: Column and widget descriptions now support links in text.

Test Plan: Updated

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3981
This commit is contained in:
Jarosław Sadziński
2023-08-04 13:32:53 +02:00
parent 5dfa9a542c
commit 00b88fd683
12 changed files with 260 additions and 120 deletions

View File

@@ -6,3 +6,31 @@ declare namespace Chai {
notIncludeMembers<T>(superset: T[], subset: T[], message?: string): void;
}
}
declare module "selenium-webdriver" {
interface WebDriver {
withActions(cb: (actions: WebActions) => void): Promise<void>;
}
// This is not a complete definition of available methods, but only those that we use for now.
// TODO: find documentation for this interface or update selenium-webdriver.
interface WebActions {
contextClick(el?: WebElement): WebActions;
click(el?: WebElement): WebActions;
press(): WebActions;
move(params: {origin?: WebElement|string, x?: number, y?: number}): WebActions;
keyDown(key: string): WebActions;
keyUp(key: string): WebActions;
dragAndDrop(element: WebElement, target: WebElement): WebActions;
release(): WebActions;
doubleClick(element: WebElement): WebActions;
pause(ms: number): WebActions;
}
}
import "mocha-webdriver";
declare module "mocha-webdriver" {
// It looks like this hack makes tsc see our definition as primary, adding
// the typed version override (of the withActions method) as the default one.
export declare let driver: import("selenium-webdriver").WebDriver;
}

View File

@@ -100,6 +100,43 @@ describe('DescriptionColumn', function() {
assert.isFalse(await gu.getColumnHeader({col: 'D'}).isPresent());
});
it('shows links in the column description', async () => {
const revert = await gu.begin();
// Add a column and add a description with a link.
await addColumn();
await clickAddDescription();
await gu.sendKeys('First line');
await gu.sendKeys(Key.SHIFT, Key.ENTER, Key.NULL);
await gu.sendKeys('Second line https://example.com');
await gu.sendKeys(Key.SHIFT, Key.ENTER, Key.NULL);
await gu.sendKeys('Third line');
await pressSave();
const header = await gu.getColumnHeader({col: 'D'});
// 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 have a link there.
const testTooltip = async () => {
const tooltip = driver.find(".test-tooltip");
assert.equal(await tooltip.find(".test-text-link a").getAttribute('href'), "https://example.com/");
assert.equal(await tooltip.find(".test-text-link").getText(), "https://example.com");
assert.equal(await tooltip.getText(), "First line\nSecond line \nhttps://example.com\nThird line");
};
await testTooltip();
// Convert it to a card view.
await gu.changeWidget('Card');
await openCardColumnTooltip('D');
await testTooltip();
await revert();
});
it('should close popup by enter and escape', async () => {
// Add another column, make sure that enter and escape work.
await addColumn();
@@ -387,13 +424,14 @@ describe('DescriptionColumn', function() {
const doc = await mainSession.tempDoc(cleanup, "CardView.grist", { load: true });
const docId = doc.id;
// Make more room for switching between columns.
await gu.toggleSidePanel('left', 'close');
await gu.openColumnPanel();
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();
@@ -408,6 +446,7 @@ describe('DescriptionColumn', function() {
await gu.getCell({ rowNum: 1, col: 'B' }).click();
assert.equal(await getDescriptionInput().value(), '');
await gu.toggleSidePanel('left', 'open');
});
it('should show info tooltip only if there is a description', async () => {
@@ -428,16 +467,7 @@ describe('DescriptionColumn', function() {
.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();
await openCardColumnTooltip('B');
// Check the content of the tooltip
const descriptionTooltip = await driver
@@ -561,3 +591,15 @@ async function saveVisible() {
async function cancelVisible() {
return await driver.find(".test-column-title-cancel").isDisplayed();
}
async function openCardColumnTooltip(col: string) {
const detailDescribedColumnFirstRow = await gu.getDetailCell(col, 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();
}

View File

@@ -7,14 +7,17 @@ describe('DescriptionWidget', function() {
this.timeout(20000);
const cleanup = setupTestSuite();
it('should support basic edition in right panel', async () => {
before(async () => {
const mainSession = await gu.session().teamSite.login();
await mainSession.tempDoc(cleanup, "CardView.grist", { load: true });
await gu.openWidgetPanel();
});
it('should support basic edition in right panel', async () => {
const newWidgetDesc = "This is the widget description\nIt is in two lines";
await gu.toggleSidePanel('right', 'open');
const rightPanelDescriptionInput = await driver.find('.test-right-panel .test-right-widget-description');
await rightPanelDescriptionInput.click();
await gu.clearInput();
await rightPanelDescriptionInput.sendKeys(newWidgetDesc);
// Click on other input to unselect descriptionInput
await driver.find('.test-right-panel .test-right-widget-title').click();
@@ -22,9 +25,6 @@ describe('DescriptionWidget', function() {
});
it('should support basic edition in widget popup', async () => {
const mainSession = await gu.session().teamSite.login();
await mainSession.tempDoc(cleanup, "CardView.grist", { load: true });
const widgetName = "Table";
const newWidgetDescFirstLine = "First line of the description";
const newWidgetDescSecondLine = "Second line of the description";
@@ -34,9 +34,6 @@ describe('DescriptionWidget', function() {
});
it('should show info tooltip only if there is a description', async () => {
const mainSession = await gu.session().teamSite.login();
await mainSession.tempDoc(cleanup, "CardView.grist", { load: true });
const newWidgetDesc = "New description for widget Table";
await addWidgetDescription("Table", newWidgetDesc);
@@ -46,6 +43,21 @@ describe('DescriptionWidget', function() {
await checkDescValueInWidgetTooltip("Table", newWidgetDesc);
});
it('shows link in a description', async () => {
await addWidgetDescription("Table", "Some text with a https://www.grist.com link");
assert.isFalse(await getWidgetTooltip("Single card").isPresent());
assert.isTrue(await getWidgetTooltip("Table").isPresent());
await getWidgetTooltip("Table").click();
await waitForTooltip();
const descriptionTooltip = await driver
.find('.test-widget-info-tooltip-popup');
assert.equal(await descriptionTooltip.getText(), "Some text with a \nhttps://www.grist.com link");
assert.equal(await descriptionTooltip.find(".test-text-link a").getAttribute('href'), "https://www.grist.com/");
assert.equal(await descriptionTooltip.find(".test-text-link").getText(), "https://www.grist.com");
});
});
async function waitForEditPopup() {
@@ -77,6 +89,7 @@ async function addWidgetDescription(widgetName: string, desc: string, descSecond
// Edit the description of the widget inside the popup
await widgetDescInput.click();
await gu.clearInput();
await widgetDescInput.sendKeys(desc);
if (descSecondLine !== "") {
await widgetDescInput.sendKeys(Key.ENTER, descSecondLine);

View File

@@ -4359,9 +4359,7 @@ async function getWorkspaceId(api: UserAPIImpl, name: string) {
return workspaces.find((w) => w.name === name)!.id;
}
// TODO: deal with safe port allocation
const webhooksTestPort = 34365;
const webhooksTestPort = Number(process.env.WEBHOOK_TEST_PORT);
async function setupDataDir(dir: string) {
// we'll be serving Hello.grist content for various document ids, so let's make copies of it in

View File

@@ -51,11 +51,8 @@ function backupEnvironmentVariables() {
});
}
/*
TODO: this hardcoded port numbers might cause conflicts in parallel tests executions. replace with someone more generic
*/
const webhooksTestPort = 34365;
const webhooksTestProxyPort = 22335;
const webhooksTestPort = Number(process.env.WEBHOOK_TEST_PORT);
const webhooksTestProxyPort = Number(process.env.WEBHOOK_TEST_PROXY_PORT);
describe('Webhooks-Proxy', function () {
// A testDir of the form grist_test_{USER}_{SERVER_NAME}