mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Add custom widget gallery
Summary: Custom widgets are now shown in a gallery. The gallery is automatically opened when a new custom widget is added to a page. Descriptions, authors, and update times are pulled from the widget manifest. Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Subscribers: dsagal Differential Revision: https://phab.getgrist.com/D4309
This commit is contained in:
@@ -24,13 +24,6 @@ describe('AttachedCustomWidget', function () {
|
||||
let widgetServerUrl = '';
|
||||
// Holds widgets manifest content.
|
||||
let widgets: ICustomWidget[] = [];
|
||||
// Switches widget manifest url
|
||||
async function useManifest(url: string) {
|
||||
await server.testingHooks.setWidgetRepositoryUrl(url ? `${widgetServerUrl}${url}` : '');
|
||||
await driver.executeAsyncScript(
|
||||
(done: any) => (window as any).gristApp?.topAppModel.testReloadWidgets().then(done).catch(done) || done()
|
||||
);
|
||||
}
|
||||
|
||||
async function buildWidgetServer(){
|
||||
// Create simple widget server that serves manifest.json file, some widgets and some error pages.
|
||||
@@ -69,12 +62,11 @@ describe('AttachedCustomWidget', function () {
|
||||
before(async function () {
|
||||
await buildWidgetServer();
|
||||
oldEnv = new EnvironmentSnapshot();
|
||||
process.env.GRIST_WIDGET_LIST_URL = `${widgetServerUrl}${manifestEndpoint}`;
|
||||
process.env.PERMITTED_CUSTOM_WIDGETS = "calendar";
|
||||
await server.restart();
|
||||
await useManifest(manifestEndpoint);
|
||||
const session = await gu.session().login();
|
||||
await session.tempDoc(cleanup, 'Hello.grist');
|
||||
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
|
||||
@@ -145,18 +145,6 @@ describe('BehavioralPrompts', function() {
|
||||
await assertPromptTitle('Editing Card Layout');
|
||||
});
|
||||
|
||||
it('should be shown after adding custom view as a new page', async function() {
|
||||
await gu.addNewPage('Custom', 'Table1');
|
||||
await assertPromptTitle('Custom Widgets');
|
||||
await gu.undo();
|
||||
});
|
||||
|
||||
it('should be shown after adding custom section', async function() {
|
||||
await gu.addNewSection('Custom', 'Table1');
|
||||
await assertPromptTitle('Custom Widgets');
|
||||
await gu.undo();
|
||||
});
|
||||
|
||||
describe('for the Add New button', function() {
|
||||
it('should not be shown if site is empty', async function() {
|
||||
session = await gu.session().user('user4').login({showTips: true});
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
import {safeJsonParse} from 'app/common/gutil';
|
||||
import * as chai from 'chai';
|
||||
import {assert, driver, Key} from 'mocha-webdriver';
|
||||
import {serveCustomViews, Serving, setAccess} from 'test/nbrowser/customUtil';
|
||||
import * as gu from 'test/nbrowser/gristUtils';
|
||||
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
|
||||
|
||||
import { serveCustomViews, Serving, setAccess } from 'test/nbrowser/customUtil';
|
||||
|
||||
import * as chai from 'chai';
|
||||
chai.config.truncateThreshold = 5000;
|
||||
|
||||
async function setCustomWidget() {
|
||||
// if there is a select widget option
|
||||
if (await driver.find('.test-config-widget-select').isPresent()) {
|
||||
const selected = await driver.find('.test-config-widget-select .test-select-open').getText();
|
||||
if (selected != "Custom URL") {
|
||||
await driver.find('.test-config-widget-select .test-select-open').click();
|
||||
await driver.findContent('.test-select-menu li', "Custom URL").click();
|
||||
await gu.waitForServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('CustomView', function() {
|
||||
this.timeout(20000);
|
||||
gu.bigScreen();
|
||||
@@ -49,9 +36,8 @@ describe('CustomView', function() {
|
||||
await gu.addNewSection('Custom', 'Table1');
|
||||
|
||||
// Point to a widget that doesn't immediately call ready.
|
||||
await gu.setCustomWidgetUrl(`${serving.url}/deferred-ready`, {openGallery: false});
|
||||
await gu.toggleSidePanel('right', 'open');
|
||||
await driver.find('.test-config-widget-url').click();
|
||||
await gu.sendKeys(`${serving.url}/deferred-ready`, Key.ENTER);
|
||||
|
||||
// We should have a single iframe.
|
||||
assert.equal(await driver.findAll('iframe').then(f => f.length), 1);
|
||||
@@ -108,10 +94,8 @@ describe('CustomView', function() {
|
||||
|
||||
// Replace the widget with a custom widget that just reads out the data
|
||||
// as JSON.
|
||||
await driver.find('.test-config-widget').click();
|
||||
await setCustomWidget();
|
||||
await driver.find('.test-config-widget-url').click();
|
||||
await driver.sendKeys(`${serving.url}/readout`, Key.ENTER);
|
||||
await gu.setCustomWidgetUrl(`${serving.url}/readout`, {openGallery: false});
|
||||
await gu.openWidgetPanel();
|
||||
await setAccess(access);
|
||||
await gu.waitForServer();
|
||||
|
||||
@@ -167,10 +151,8 @@ describe('CustomView', function() {
|
||||
await gu.waitForServer();
|
||||
|
||||
// Choose the custom view that just reads out data as json
|
||||
await driver.find('.test-config-widget').click();
|
||||
await setCustomWidget();
|
||||
await driver.find('.test-config-widget-url').click();
|
||||
await driver.sendKeys(`${serving.url}/readout`, Key.ENTER);
|
||||
await gu.setCustomWidgetUrl(`${serving.url}/readout`, {openGallery: false});
|
||||
await gu.openWidgetPanel();
|
||||
await setAccess(access);
|
||||
await gu.waitForServer();
|
||||
|
||||
@@ -265,7 +247,7 @@ describe('CustomView', function() {
|
||||
it('allows switching to custom section by clicking inside it', async function() {
|
||||
await gu.getCell({section: 'FRIENDS', col: 0, rowNum: 1}).click();
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'FRIENDS');
|
||||
assert.equal(await driver.find('.test-config-widget-url').isPresent(), false);
|
||||
assert.equal(await driver.find('.test-config-widget-open-custom-widget-gallery').isPresent(), false);
|
||||
|
||||
const iframe = gu.getSection('Friends custom').find('iframe');
|
||||
await driver.switchTo().frame(iframe);
|
||||
@@ -274,24 +256,19 @@ describe('CustomView', function() {
|
||||
// Check that the right section is active, and its settings visible in the side panel.
|
||||
await driver.switchTo().defaultContent();
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'FRIENDS Custom');
|
||||
assert.equal(await driver.find('.test-config-widget-url').isPresent(), true);
|
||||
assert.equal(await driver.find('.test-config-widget-open-custom-widget-gallery').isPresent(), true);
|
||||
|
||||
// Switch back.
|
||||
await gu.getCell({section: 'FRIENDS', col: 0, rowNum: 1}).click();
|
||||
assert.equal(await gu.getActiveSectionTitle(), 'FRIENDS');
|
||||
assert.equal(await driver.find('.test-config-widget-url').isPresent(), false);
|
||||
assert.equal(await driver.find('.test-config-widget-open-custom-widget-gallery').isPresent(), false);
|
||||
});
|
||||
|
||||
it('deals correctly with requests that require full access', async function() {
|
||||
// Choose a custom widget that tries to replace all cells in all user tables with 'zap'.
|
||||
await gu.getSection('Friends Custom').click();
|
||||
await driver.find('.test-config-widget').click();
|
||||
await setAccess("none");
|
||||
await gu.waitForServer();
|
||||
|
||||
await gu.setValue(driver.find('.test-config-widget-url'), '');
|
||||
await driver.find('.test-config-widget-url').click();
|
||||
await driver.sendKeys(`${serving.url}/zap`, Key.ENTER);
|
||||
await gu.setCustomWidgetUrl(`${serving.url}/zap`);
|
||||
await gu.openWidgetPanel();
|
||||
await setAccess(access);
|
||||
await gu.waitForServer();
|
||||
|
||||
@@ -329,12 +306,10 @@ describe('CustomView', function() {
|
||||
// The test doc already has a Custom View widget. It just needs to
|
||||
// have a URL set.
|
||||
await gu.getSection('TYPES custom').click();
|
||||
await driver.find('.test-config-widget').click();
|
||||
await setCustomWidget();
|
||||
await gu.setCustomWidgetUrl(`${serving.url}/types`);
|
||||
// If we needed to change widget to Custom URL, make sure access is read table.
|
||||
await setAccess("read table");
|
||||
await driver.find('.test-config-widget-url').click();
|
||||
await driver.sendKeys(`${serving.url}/types`, Key.ENTER);
|
||||
await gu.waitForServer();
|
||||
|
||||
const iframe = gu.getSection('TYPES custom').find('iframe');
|
||||
await driver.switchTo().frame(iframe);
|
||||
@@ -480,10 +455,8 @@ describe('CustomView', function() {
|
||||
await gu.waitForServer();
|
||||
|
||||
// Select a custom widget that tries to replace all cells in all user tables with 'zap'.
|
||||
await driver.find('.test-config-widget').click();
|
||||
await setCustomWidget();
|
||||
await driver.find('.test-config-widget-url').click();
|
||||
await driver.sendKeys(`${serving.url}/zap`, Key.ENTER);
|
||||
await gu.setCustomWidgetUrl(`${serving.url}/zap`, {openGallery: false});
|
||||
await gu.openWidgetPanel();
|
||||
await setAccess("full");
|
||||
await gu.waitForServer();
|
||||
|
||||
@@ -537,10 +510,10 @@ describe('CustomView', function() {
|
||||
const doc = await mainSession.tempDoc(cleanup, 'FetchSelectedOptions.grist', {load: false});
|
||||
await mainSession.loadDoc(`/doc/${doc.id}`);
|
||||
|
||||
await gu.toggleSidePanel('right', 'open');
|
||||
await gu.getSection('TABLE1 Custom').click();
|
||||
await driver.find('.test-config-widget-url').click();
|
||||
await gu.sendKeys(`${serving.url}/fetchSelectedOptions`, Key.ENTER);
|
||||
await gu.setCustomWidgetUrl(`${serving.url}/fetchSelectedOptions`);
|
||||
await gu.openWidgetPanel();
|
||||
await setAccess("full");
|
||||
await gu.waitForServer();
|
||||
|
||||
const expected = {
|
||||
@@ -620,8 +593,10 @@ describe('CustomView', function() {
|
||||
}
|
||||
|
||||
await inFrame(async () => {
|
||||
const parsed = await getData(12);
|
||||
assert.deepEqual(parsed, expected);
|
||||
await gu.waitToPass(async () => {
|
||||
const parsed = await getData(12);
|
||||
assert.deepEqual(parsed, expected);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Change the access level away from 'full'.
|
||||
|
||||
@@ -20,20 +20,46 @@ const widgetEndpoint = '/widget';
|
||||
const CUSTOM_URL = 'Custom URL';
|
||||
|
||||
// Create some widgets:
|
||||
const widget1: ICustomWidget = {widgetId: '1', name: 'W1', url: widgetEndpoint + '?name=W1'};
|
||||
const widget2: ICustomWidget = {widgetId: '2', name: 'W2', url: widgetEndpoint + '?name=W2'};
|
||||
const widget1: ICustomWidget = {
|
||||
widgetId: '1',
|
||||
name: 'W1',
|
||||
url: widgetEndpoint + '?name=W1',
|
||||
description: 'Widget 1 description',
|
||||
authors: [
|
||||
{
|
||||
name: 'Developer 1',
|
||||
},
|
||||
{
|
||||
name: 'Developer 2',
|
||||
},
|
||||
],
|
||||
isGristLabsMaintained: true,
|
||||
lastUpdatedAt: '2024-07-30T00:13:31-04:00',
|
||||
};
|
||||
const widget2: ICustomWidget = {
|
||||
widgetId: '2',
|
||||
name: 'W2',
|
||||
url: widgetEndpoint + '?name=W2',
|
||||
};
|
||||
const widgetWithTheme: ICustomWidget = {
|
||||
widgetId: '3',
|
||||
name: 'WithTheme',
|
||||
url: widgetEndpoint + '?name=WithTheme',
|
||||
isGristLabsMaintained: true,
|
||||
};
|
||||
const widgetNoPluginApi: ICustomWidget = {
|
||||
widgetId: '4',
|
||||
name: 'NoPluginApi',
|
||||
url: widgetEndpoint + '?name=NoPluginApi',
|
||||
isGristLabsMaintained: true,
|
||||
};
|
||||
const fromAccess = (level: AccessLevel) =>
|
||||
({widgetId: level, name: level, url: widgetEndpoint, accessLevel: level}) as ICustomWidget;
|
||||
const fromAccess = (level: AccessLevel): ICustomWidget => ({
|
||||
widgetId: level,
|
||||
name: level,
|
||||
url: widgetEndpoint,
|
||||
accessLevel: level,
|
||||
isGristLabsMaintained: true,
|
||||
});
|
||||
const widgetNone = fromAccess(AccessLevel.none);
|
||||
const widgetRead = fromAccess(AccessLevel.read_table);
|
||||
const widgetFull = fromAccess(AccessLevel.full);
|
||||
@@ -51,23 +77,27 @@ describe('CustomWidgets', function () {
|
||||
gu.bigScreen();
|
||||
const cleanup = setupTestSuite();
|
||||
|
||||
let oldEnv: EnvironmentSnapshot;
|
||||
|
||||
// Holds url for sample widget server.
|
||||
let widgetServerUrl = '';
|
||||
|
||||
// Switches widget manifest url
|
||||
async function useManifest(url: string) {
|
||||
await server.testingHooks.setWidgetRepositoryUrl(url ? `${widgetServerUrl}${url}` : '');
|
||||
}
|
||||
|
||||
async function reloadWidgets() {
|
||||
await driver.executeAsyncScript(
|
||||
(done: any) => (window as any).gristApp?.topAppModel.testReloadWidgets().then(done).catch(done) || done()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
before(async function () {
|
||||
if (server.isExternalServer()) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
// Create simple widget server that serves manifest.json file, some widgets and some error pages.
|
||||
const widgetServer = await serveSomething(app => {
|
||||
app.get('/404', (_, res) => res.sendStatus(404).end()); // not found
|
||||
@@ -105,32 +135,31 @@ describe('CustomWidgets', function () {
|
||||
cleanup.addAfterAll(widgetServer.shutdown);
|
||||
widgetServerUrl = widgetServer.url;
|
||||
|
||||
// Start with valid endpoint and 2 widgets.
|
||||
oldEnv = new EnvironmentSnapshot();
|
||||
process.env.GRIST_WIDGET_LIST_URL = `${widgetServerUrl}${manifestEndpoint}`;
|
||||
await server.restart();
|
||||
|
||||
// Start with 2 widgets.
|
||||
widgets = [widget1, widget2];
|
||||
await useManifest(manifestEndpoint);
|
||||
|
||||
const session = await gu.session().login();
|
||||
await session.tempDoc(cleanup, 'Hello.grist');
|
||||
// Add custom section.
|
||||
await gu.addNewSection(/Custom/, /Table1/, {selectBy: /TABLE1/});
|
||||
|
||||
// Add custom section.
|
||||
await gu.addNewSection(/Custom/, /Table1/, {customWidget: /Custom URL/, selectBy: /TABLE1/});
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
await server.testingHooks.setWidgetRepositoryUrl('');
|
||||
oldEnv.restore();
|
||||
await server.restart();
|
||||
});
|
||||
|
||||
// Open or close widget menu.
|
||||
const toggle = async () => await driver.findWait('.test-config-widget-select .test-select-open', 1000).click();
|
||||
// Get current value from widget menu.
|
||||
const current = () => driver.find('.test-config-widget-select .test-select-open').getText();
|
||||
// Get options from widget menu (must be first opened).
|
||||
const options = () => driver.findAll('.test-select-menu li', e => e.getText());
|
||||
// Select widget from the menu.
|
||||
const select = async (text: string | RegExp) => {
|
||||
await driver.findContent('.test-select-menu li', text).click();
|
||||
await gu.waitForServer();
|
||||
};
|
||||
afterEach(() => gu.checkForErrors());
|
||||
|
||||
// Get available widgets from widget gallery (must be first opened).
|
||||
const galleryWidgets = () => driver.findAll('.test-custom-widget-gallery-widget-name', e => e.getText());
|
||||
|
||||
// Get rendered content from custom section.
|
||||
const content = async () => {
|
||||
return gu.doInIframe(await getCustomWidgetFrame(), async ()=>{
|
||||
@@ -169,19 +198,6 @@ describe('CustomWidgets', function () {
|
||||
return result === "__undefined__" ? undefined : result;
|
||||
});
|
||||
}
|
||||
// Replace url for the Custom URL widget.
|
||||
const setUrl = async (url: string) => {
|
||||
await driver.find('.test-config-widget-url').click();
|
||||
// First clear textbox.
|
||||
await gu.sendKeys(await gu.selectAllKey(), Key.DELETE);
|
||||
if (url) {
|
||||
await gu.sendKeys(`${widgetServerUrl}${url}`, Key.ENTER);
|
||||
} else {
|
||||
await gu.sendKeys(Key.ENTER);
|
||||
}
|
||||
};
|
||||
// Get an URL from the URL textbox.
|
||||
const getUrl = () => driver.find('.test-config-widget-url').value();
|
||||
// Get first error message from error toasts.
|
||||
const getErrorMessage = async () => (await gu.getToasts())[0];
|
||||
// Changes active section to recreate creator panel.
|
||||
@@ -215,8 +231,6 @@ describe('CustomWidgets', function () {
|
||||
const reject = () => driver.find(".test-config-widget-access-reject").click();
|
||||
|
||||
async function enableWidgetsAndShowPanel() {
|
||||
// Override gristConfig to enable widget list.
|
||||
await driver.executeScript('window.gristConfig.enableWidgetRepository = true;');
|
||||
// We need to be sure that widget configuration panel is open all the time.
|
||||
await gu.toggleSidePanel('right', 'open');
|
||||
await recreatePanel();
|
||||
@@ -226,68 +240,65 @@ describe('CustomWidgets', function () {
|
||||
describe('RightWidgetMenu', () => {
|
||||
beforeEach(enableWidgetsAndShowPanel);
|
||||
|
||||
it('should show widgets in dropdown', async () => {
|
||||
await gu.toggleSidePanel('right', 'open');
|
||||
await driver.find('.test-right-tab-pagewidget').click();
|
||||
await gu.waitForServer();
|
||||
await driver.find('.test-config-widget').click();
|
||||
await gu.waitForServer(); // Wait for widgets to load.
|
||||
afterEach(() => gu.checkForErrors());
|
||||
|
||||
// Selectbox should have select label.
|
||||
assert.equal(await current(), CUSTOM_URL);
|
||||
|
||||
// There should be 3 options (together with Custom URL)
|
||||
await toggle();
|
||||
assert.deepEqual(await options(), [CUSTOM_URL, widget1.name, widget2.name]);
|
||||
await toggle();
|
||||
it('should show button to open gallery', async () => {
|
||||
const button = await driver.find('.test-config-widget-open-custom-widget-gallery');
|
||||
assert.equal(await button.getText(), 'Custom URL');
|
||||
await button.click();
|
||||
assert.isTrue(await driver.find('.test-custom-widget-gallery-container').isDisplayed());
|
||||
await gu.sendKeys(Key.ESCAPE, Key.ESCAPE);
|
||||
assert.isFalse(await driver.find('.test-custom-widget-gallery-container').isPresent());
|
||||
});
|
||||
|
||||
it('should switch between widgets', async () => {
|
||||
// Test custom URL.
|
||||
await toggle();
|
||||
await select(CUSTOM_URL);
|
||||
assert.equal(await current(), CUSTOM_URL);
|
||||
assert.equal(await getUrl(), '');
|
||||
await setUrl('/200');
|
||||
// Test Custom URL.
|
||||
assert.equal(await gu.getCustomWidgetName(), CUSTOM_URL);
|
||||
assert.isTrue((await content()).startsWith('Custom widget'));
|
||||
await gu.setCustomWidgetUrl(`${widgetServerUrl}/200`);
|
||||
assert.equal(await gu.getCustomWidgetName(), CUSTOM_URL);
|
||||
assert.equal(await content(), 'OK');
|
||||
|
||||
// Test first widget.
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
assert.equal(await current(), widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
assert.equal(await gu.getCustomWidgetName(), widget1.name);
|
||||
assert.equal(await gu.getCustomWidgetInfo('description'), widget1.description);
|
||||
assert.equal(await gu.getCustomWidgetInfo('developer'), widget1.authors?.[0].name);
|
||||
assert.equal(await gu.getCustomWidgetInfo('last-updated'), 'July 30, 2024');
|
||||
assert.equal(await content(), widget1.name);
|
||||
|
||||
// Test second widget.
|
||||
await toggle();
|
||||
await select(widget2.name);
|
||||
assert.equal(await current(), widget2.name);
|
||||
await gu.setCustomWidget(widget2.name);
|
||||
assert.equal(await gu.getCustomWidgetName(), widget2.name);
|
||||
assert.equal(await gu.getCustomWidgetInfo('description'), '');
|
||||
assert.equal(await gu.getCustomWidgetInfo('developer'), '');
|
||||
assert.equal(await gu.getCustomWidgetInfo('last-updated'), '');
|
||||
assert.equal(await content(), widget2.name);
|
||||
|
||||
// Go back to Custom URL.
|
||||
await toggle();
|
||||
await select(CUSTOM_URL);
|
||||
assert.equal(await getUrl(), '');
|
||||
assert.equal(await current(), CUSTOM_URL);
|
||||
await setUrl('/200');
|
||||
await gu.setCustomWidget(CUSTOM_URL);
|
||||
assert.equal(await gu.getCustomWidgetName(), CUSTOM_URL);
|
||||
assert.isTrue((await content()).startsWith('Custom widget'));
|
||||
await gu.setCustomWidgetUrl(`${widgetServerUrl}/200`);
|
||||
assert.equal(await gu.getCustomWidgetName(), CUSTOM_URL);
|
||||
assert.equal(await content(), 'OK');
|
||||
|
||||
// Clear url and test if message page is shown.
|
||||
await setUrl('');
|
||||
assert.equal(await current(), CUSTOM_URL);
|
||||
assert.isTrue((await content()).startsWith('Custom widget')); // start page
|
||||
await gu.setCustomWidgetUrl('');
|
||||
assert.equal(await gu.getCustomWidgetName(), CUSTOM_URL);
|
||||
assert.isTrue((await content()).startsWith('Custom widget'));
|
||||
|
||||
await recreatePanel();
|
||||
assert.equal(await current(), CUSTOM_URL);
|
||||
await gu.undo(7);
|
||||
assert.equal(await gu.getCustomWidgetName(), CUSTOM_URL);
|
||||
await gu.undo(6);
|
||||
});
|
||||
|
||||
it('should support theme variables', async () => {
|
||||
widgets = [widgetWithTheme];
|
||||
await useManifest(manifestEndpoint);
|
||||
await reloadWidgets();
|
||||
await recreatePanel();
|
||||
await toggle();
|
||||
await select(widgetWithTheme.name);
|
||||
assert.equal(await current(), widgetWithTheme.name);
|
||||
await gu.setCustomWidget(widgetWithTheme.name);
|
||||
assert.equal(await gu.getCustomWidgetName(), widgetWithTheme.name);
|
||||
assert.equal(await content(), widgetWithTheme.name);
|
||||
|
||||
const getWidgetColor = async () => {
|
||||
@@ -316,18 +327,14 @@ describe('CustomWidgets', function () {
|
||||
|
||||
// Check that the widget is back to using the GristLight text color.
|
||||
assert.equal(await getWidgetColor(), 'rgba(38, 38, 51, 1)');
|
||||
|
||||
// Re-enable widget repository.
|
||||
await driver.executeScript('window.gristConfig.enableWidgetRepository = true;');
|
||||
});
|
||||
|
||||
it("should support widgets that don't use the plugin api", async () => {
|
||||
widgets = [widgetNoPluginApi];
|
||||
await useManifest(manifestEndpoint);
|
||||
await reloadWidgets();
|
||||
await recreatePanel();
|
||||
await toggle();
|
||||
await select(widgetNoPluginApi.name);
|
||||
assert.equal(await current(), widgetNoPluginApi.name);
|
||||
await gu.setCustomWidget(widgetNoPluginApi.name);
|
||||
assert.equal(await gu.getCustomWidgetName(), widgetNoPluginApi.name);
|
||||
|
||||
// Check that the widget loaded and its iframe is visible.
|
||||
assert.equal(await content(), widgetNoPluginApi.name);
|
||||
@@ -335,7 +342,7 @@ describe('CustomWidgets', function () {
|
||||
|
||||
// Revert to original configuration.
|
||||
widgets = [widget1, widget2];
|
||||
await useManifest(manifestEndpoint);
|
||||
await reloadWidgets();
|
||||
await recreatePanel();
|
||||
});
|
||||
|
||||
@@ -343,13 +350,15 @@ describe('CustomWidgets', function () {
|
||||
const testError = async (url: string, error: string) => {
|
||||
// Switch section to rebuild the creator panel.
|
||||
await useManifest(url);
|
||||
await reloadWidgets();
|
||||
await recreatePanel();
|
||||
assert.include(await getErrorMessage(), error);
|
||||
await gu.wipeToasts();
|
||||
// List should contain only a Custom URL.
|
||||
await toggle();
|
||||
assert.deepEqual(await options(), [CUSTOM_URL]);
|
||||
await toggle();
|
||||
// Gallery should only contain the Custom URL widget.
|
||||
await gu.openCustomWidgetGallery();
|
||||
assert.deepEqual(await galleryWidgets(), [CUSTOM_URL]);
|
||||
await gu.wipeToasts();
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
};
|
||||
|
||||
await testError('/404', "Remote widget list not found");
|
||||
@@ -361,6 +370,7 @@ describe('CustomWidgets', function () {
|
||||
|
||||
// Reset to valid manifest.
|
||||
await useManifest(manifestEndpoint);
|
||||
await reloadWidgets();
|
||||
await recreatePanel();
|
||||
});
|
||||
|
||||
@@ -371,15 +381,14 @@ describe('CustomWidgets', function () {
|
||||
*/
|
||||
it.skip('should show widget when it was removed from list', async () => {
|
||||
// Select widget1 and then remove it from the list.
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
widgets = [widget2];
|
||||
// Invalidate cache.
|
||||
await useManifest(manifestEndpoint);
|
||||
await reloadWidgets();
|
||||
// Toggle sections to reset creator panel and fetch list of available widgets.
|
||||
await recreatePanel();
|
||||
// But still should be selected with a correct url.
|
||||
assert.equal(await current(), widget1.name);
|
||||
assert.equal(await gu.getCustomWidgetName(), widget1.name);
|
||||
assert.equal(await content(), widget1.name);
|
||||
await gu.undo(1);
|
||||
});
|
||||
@@ -387,26 +396,22 @@ describe('CustomWidgets', function () {
|
||||
it('should switch access level to none on new widget', async () => {
|
||||
widgets = [widget1, widget2];
|
||||
await recreatePanel();
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
await access(AccessLevel.full);
|
||||
assert.equal(await access(), AccessLevel.full);
|
||||
|
||||
await toggle();
|
||||
await select(widget2.name);
|
||||
await gu.setCustomWidget(widget2.name);
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
await access(AccessLevel.full);
|
||||
assert.equal(await access(), AccessLevel.full);
|
||||
|
||||
await toggle();
|
||||
await select(CUSTOM_URL);
|
||||
await gu.setCustomWidget(CUSTOM_URL);
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
await access(AccessLevel.full);
|
||||
assert.equal(await access(), AccessLevel.full);
|
||||
|
||||
await toggle();
|
||||
await select(widget2.name);
|
||||
await gu.setCustomWidget(widget2.name);
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
await access(AccessLevel.full);
|
||||
assert.equal(await access(), AccessLevel.full);
|
||||
@@ -416,19 +421,18 @@ describe('CustomWidgets', function () {
|
||||
|
||||
it('should prompt for access change', async () => {
|
||||
widgets = [widget1, widget2, widgetFull, widgetNone, widgetRead];
|
||||
await useManifest(manifestEndpoint);
|
||||
await reloadWidgets();
|
||||
await recreatePanel();
|
||||
|
||||
const test = async (w: ICustomWidget) => {
|
||||
// Select widget without desired access level
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
assert.isFalse(await hasPrompt());
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
|
||||
// Select one with desired access level
|
||||
await toggle();
|
||||
await select(w.name);
|
||||
await gu.setCustomWidget(w.name);
|
||||
|
||||
// Access level should be still none (test by content which will display access level from query string)
|
||||
assert.equal(await content(), AccessLevel.none);
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
@@ -440,13 +444,11 @@ describe('CustomWidgets', function () {
|
||||
assert.equal(await access(), w.accessLevel);
|
||||
|
||||
// Do the same, but this time reject
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
assert.isFalse(await hasPrompt());
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
|
||||
await toggle();
|
||||
await select(w.name);
|
||||
await gu.setCustomWidget(w.name);
|
||||
assert.isTrue(await hasPrompt());
|
||||
assert.equal(await content(), AccessLevel.none);
|
||||
|
||||
@@ -462,14 +464,12 @@ describe('CustomWidgets', function () {
|
||||
|
||||
it('should auto accept none access level', async () => {
|
||||
// Select widget without access level
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
assert.isFalse(await hasPrompt());
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
|
||||
// Switch to one with none access level
|
||||
await toggle();
|
||||
await select(widgetNone.name);
|
||||
await gu.setCustomWidget(widgetNone.name);
|
||||
assert.isFalse(await hasPrompt());
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
assert.equal(await content(), AccessLevel.none);
|
||||
@@ -477,14 +477,12 @@ describe('CustomWidgets', function () {
|
||||
|
||||
it('should show prompt when user switches sections', async () => {
|
||||
// Select widget without access level
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
assert.isFalse(await hasPrompt());
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
|
||||
// Switch to one with full access level
|
||||
await toggle();
|
||||
await select(widgetFull.name);
|
||||
await gu.setCustomWidget(widgetFull.name);
|
||||
assert.isTrue(await hasPrompt());
|
||||
|
||||
// Switch section, and test if prompt is hidden
|
||||
@@ -496,19 +494,16 @@ describe('CustomWidgets', function () {
|
||||
|
||||
it('should hide prompt when user switches widget', async () => {
|
||||
// Select widget without access level
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
assert.isFalse(await hasPrompt());
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
|
||||
// Switch to one with full access level
|
||||
await toggle();
|
||||
await select(widgetFull.name);
|
||||
await gu.setCustomWidget(widgetFull.name);
|
||||
assert.isTrue(await hasPrompt());
|
||||
|
||||
// Switch to another level.
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
assert.isFalse(await hasPrompt());
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
});
|
||||
@@ -516,8 +511,7 @@ describe('CustomWidgets', function () {
|
||||
it('should hide prompt when manually changes access level', async () => {
|
||||
// Select widget with no access level
|
||||
const selectNone = async () => {
|
||||
await toggle();
|
||||
await select(widgetNone.name);
|
||||
await gu.setCustomWidget(widgetNone.name);
|
||||
assert.isFalse(await hasPrompt());
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
assert.equal(await content(), AccessLevel.none);
|
||||
@@ -525,8 +519,7 @@ describe('CustomWidgets', function () {
|
||||
|
||||
// Selects widget with full access level
|
||||
const selectFull = async () => {
|
||||
await toggle();
|
||||
await select(widgetFull.name);
|
||||
await gu.setCustomWidget(widgetFull.name);
|
||||
assert.isTrue(await hasPrompt());
|
||||
assert.equal(await content(), AccessLevel.none);
|
||||
assert.equal(await content(), AccessLevel.none);
|
||||
@@ -559,26 +552,140 @@ describe('CustomWidgets', function () {
|
||||
assert.equal(await access(), AccessLevel.none);
|
||||
assert.equal(await content(), AccessLevel.none);
|
||||
});
|
||||
});
|
||||
|
||||
it('should offer only custom url when disabled', async () => {
|
||||
await toggle();
|
||||
await select(CUSTOM_URL);
|
||||
describe('gallery', () => {
|
||||
afterEach(() => gu.checkForErrors());
|
||||
|
||||
it('should show available widgets', async () => {
|
||||
await gu.openCustomWidgetGallery();
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-name', (el) => el.getText()),
|
||||
['Custom URL', 'full', 'none', 'read table', 'W1', 'W2']
|
||||
);
|
||||
});
|
||||
|
||||
it('should show available metadata', async () => {
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget', (el) =>
|
||||
el.matches('.test-custom-widget-gallery-widget-custom')),
|
||||
[true, false, false, false, false, false]
|
||||
);
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget', (el) =>
|
||||
el.matches('.test-custom-widget-gallery-widget-grist')),
|
||||
[false, true, true, true, true, false]
|
||||
);
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget', (el) =>
|
||||
el.matches('.test-custom-widget-gallery-widget-community')),
|
||||
[false, false, false, false, false, true]
|
||||
);
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-description', (el) => el.getText()),
|
||||
[
|
||||
'Add a widget from outside this gallery.',
|
||||
'(Missing info)',
|
||||
'(Missing info)',
|
||||
'(Missing info)',
|
||||
'Widget 1 description',
|
||||
'(Missing info)',
|
||||
]
|
||||
);
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-developer', (el) => el.getText()),
|
||||
[
|
||||
'(Missing info)',
|
||||
'(Missing info)',
|
||||
]
|
||||
);
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-last-updated', (el) => el.getText()),
|
||||
[
|
||||
'(Missing info)',
|
||||
'(Missing info)',
|
||||
'(Missing info)',
|
||||
'July 30, 2024',
|
||||
'(Missing info)',
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should filter widgets on search', async () => {
|
||||
await driver.find('.test-custom-widget-gallery-search').click();
|
||||
await gu.sendKeys('Custom');
|
||||
await gu.waitToPass(async () => {
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-name', (el) => el.getText()),
|
||||
['Custom URL']
|
||||
);
|
||||
}, 200);
|
||||
await gu.sendKeys(await gu.selectAllKey(), Key.DELETE);
|
||||
await gu.waitToPass(async () => {
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-name', (el) => el.getText()),
|
||||
['Custom URL', 'full', 'none', 'read table', 'W1', 'W2']
|
||||
);
|
||||
}, 200);
|
||||
await gu.sendKeys('W');
|
||||
await gu.waitToPass(async () => {
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-name', (el) => el.getText()),
|
||||
['Custom URL', 'W1', 'W2']
|
||||
);
|
||||
}, 200);
|
||||
await gu.sendKeys(await gu.selectAllKey(), Key.DELETE, 'tab');
|
||||
await gu.waitToPass(async () => {
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-name', (el) => el.getText()),
|
||||
['read table']
|
||||
);
|
||||
}, 200);
|
||||
await gu.sendKeys(await gu.selectAllKey(), Key.DELETE, 'Markdown');
|
||||
await gu.waitToPass(async () => {
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-name', (el) => el.getText()),
|
||||
[]
|
||||
);
|
||||
}, 200);
|
||||
await gu.sendKeys(await gu.selectAllKey(), Key.DELETE, 'Developer 1');
|
||||
await gu.waitToPass(async () => {
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-name', (el) => el.getText()),
|
||||
['W1']
|
||||
);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it('should only show Custom URL widget when repository is disabled', async () => {
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
await driver.executeScript('window.gristConfig.enableWidgetRepository = false;');
|
||||
await recreatePanel();
|
||||
assert.isTrue(await driver.find('.test-config-widget-url').isDisplayed());
|
||||
assert.isFalse(await driver.find('.test-config-widget-select').isPresent());
|
||||
await driver.executeAsyncScript(
|
||||
(done: any) => (window as any).gristApp?.topAppModel.testReloadWidgets().then(done).catch(done) || done()
|
||||
);
|
||||
await gu.openCustomWidgetGallery();
|
||||
assert.deepEqual(
|
||||
await driver.findAll('.test-custom-widget-gallery-widget-name', (el) => el.getText()),
|
||||
['Custom URL']
|
||||
);
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
await driver.executeScript('window.gristConfig.enableWidgetRepository = true;');
|
||||
await driver.executeAsyncScript(
|
||||
(done: any) => (window as any).gristApp?.topAppModel.testReloadWidgets().then(done).catch(done) || done()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gristApiSupport', async ()=>{
|
||||
beforeEach(async function () {
|
||||
// Override gristConfig to enable widget list.
|
||||
await driver.executeScript('window.gristConfig.enableWidgetRepository = true;');
|
||||
// We need to be sure that widget configuration panel is open all the time.
|
||||
await gu.toggleSidePanel('right', 'open');
|
||||
await recreatePanel();
|
||||
await driver.findWait('.test-right-tab-pagewidget', 100).click();
|
||||
});
|
||||
|
||||
afterEach(() => gu.checkForErrors());
|
||||
|
||||
it('should set language in widget url', async () => {
|
||||
function languageMenu() {
|
||||
return gu.currentDriver().find('.test-account-page-language .test-select-open');
|
||||
@@ -602,10 +709,9 @@ describe('CustomWidgets', function () {
|
||||
}
|
||||
|
||||
widgets = [widget1];
|
||||
await useManifest(manifestEndpoint);
|
||||
await reloadWidgets();
|
||||
await gu.openWidgetPanel();
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await gu.setCustomWidget(widget1.name);
|
||||
//Switch language to Polish
|
||||
await switchLanguage('Polski');
|
||||
//Check if widgets have "pl" in url
|
||||
@@ -621,8 +727,6 @@ describe('CustomWidgets', function () {
|
||||
await gu.toggleSidePanel('right', 'open');
|
||||
await driver.find('.test-config-widget').click();
|
||||
await gu.waitForServer();
|
||||
await toggle();
|
||||
await select(widget1.name);
|
||||
await access(AccessLevel.full);
|
||||
|
||||
// Check an upsert works.
|
||||
@@ -735,6 +839,7 @@ describe('CustomWidgets', function () {
|
||||
});
|
||||
|
||||
afterEach(async function() {
|
||||
await gu.checkForErrors();
|
||||
oldEnv.restore();
|
||||
await server.restart();
|
||||
await gu.reloadDoc();
|
||||
@@ -745,10 +850,10 @@ describe('CustomWidgets', function () {
|
||||
// Double-check that using one external widget, we see
|
||||
// just that widget listed.
|
||||
widgets = [widget1];
|
||||
await useManifest(manifestEndpoint);
|
||||
await reloadWidgets();
|
||||
await enableWidgetsAndShowPanel();
|
||||
await toggle();
|
||||
assert.deepEqual(await options(), [
|
||||
await gu.openCustomWidgetGallery();
|
||||
assert.deepEqual(await galleryWidgets(), [
|
||||
CUSTOM_URL, widget1.name,
|
||||
]);
|
||||
|
||||
@@ -848,13 +953,13 @@ describe('CustomWidgets', function () {
|
||||
await gu.reloadDoc();
|
||||
|
||||
// Continue using one external widget.
|
||||
await useManifest(manifestEndpoint);
|
||||
await reloadWidgets();
|
||||
await enableWidgetsAndShowPanel();
|
||||
|
||||
// Check we see one external widget and two bundled ones.
|
||||
await toggle();
|
||||
assert.deepEqual(await options(), [
|
||||
CUSTOM_URL, widget1.name, 'P1 (My Widgets)', 'P2 (My Widgets)',
|
||||
await gu.openCustomWidgetGallery();
|
||||
assert.deepEqual(await galleryWidgets(), [
|
||||
CUSTOM_URL, 'P1 (My Widgets)', 'P2 (My Widgets)', widget1.name,
|
||||
]);
|
||||
|
||||
// Prepare to check content of widgets.
|
||||
@@ -867,24 +972,22 @@ describe('CustomWidgets', function () {
|
||||
}
|
||||
|
||||
// Check built-in P1 works as expected.
|
||||
await select(/P1/);
|
||||
assert.equal(await current(), 'P1 (My Widgets)');
|
||||
await gu.setCustomWidget(/P1/, {openGallery: false});
|
||||
assert.equal(await gu.getCustomWidgetName(), 'P1 (My Widgets)');
|
||||
await gu.waitToPass(async () => {
|
||||
assert.equal(await getWidgetText(), 'P1');
|
||||
});
|
||||
|
||||
// Check external W1 works as expected.
|
||||
await toggle();
|
||||
await select(/W1/);
|
||||
assert.equal(await current(), 'W1');
|
||||
await gu.setCustomWidget(/W1/);
|
||||
assert.equal(await gu.getCustomWidgetName(), 'W1');
|
||||
await gu.waitToPass(async () => {
|
||||
assert.equal(await getWidgetText(), 'W1');
|
||||
});
|
||||
|
||||
// Check build-in P2 works as expected.
|
||||
await toggle();
|
||||
await select(/P2/);
|
||||
assert.equal(await current(), 'P2 (My Widgets)');
|
||||
await gu.setCustomWidget(/P2/);
|
||||
assert.equal(await gu.getCustomWidgetName(), 'P2 (My Widgets)');
|
||||
await gu.waitToPass(async () => {
|
||||
assert.equal(await getWidgetText(), 'P2');
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {AccessLevel} from 'app/common/CustomWidget';
|
||||
import {addToRepl, assert, driver, Key} from 'mocha-webdriver';
|
||||
import * as gu from 'test/nbrowser/gristUtils';
|
||||
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
|
||||
import {addStatic, serveSomething} from 'test/server/customUtil';
|
||||
import {AccessLevel} from 'app/common/CustomWidget';
|
||||
import {EnvironmentSnapshot} from 'test/server/testUtils';
|
||||
|
||||
// Valid manifest url.
|
||||
const manifestEndpoint = '/manifest.json';
|
||||
@@ -16,7 +17,7 @@ const READ_WIDGET = 'Read';
|
||||
const FULL_WIDGET = 'Full';
|
||||
const COLUMN_WIDGET = 'COLUMN_WIDGET';
|
||||
const REQUIRED_WIDGET = 'REQUIRED_WIDGET';
|
||||
// Custom URL label in selectbox.
|
||||
// Custom URL label.
|
||||
const CUSTOM_URL = 'Custom URL';
|
||||
// Holds url for sample widget server.
|
||||
let widgetServerUrl = '';
|
||||
@@ -27,14 +28,9 @@ function createConfigUrl(ready?: any) {
|
||||
return ready ? `${widgetServerUrl}/config?ready=` + encodeURI(JSON.stringify(ready)) : `${widgetServerUrl}/config`;
|
||||
}
|
||||
|
||||
// Open or close widget menu.
|
||||
const click = (selector: string) => driver.find(`${selector}`).click();
|
||||
const toggleDrop = (selector: string) => click(`${selector} .test-select-open`);
|
||||
const toggleWidgetMenu = () => toggleDrop('.test-config-widget-select');
|
||||
const getOptions = () => driver.findAll('.test-select-menu li', el => el.getText());
|
||||
// Get current value from widget menu.
|
||||
const currentWidget = () => driver.find('.test-config-widget-select .test-select-open').getText();
|
||||
// Select widget from the menu.
|
||||
const clickOption = async (text: string | RegExp) => {
|
||||
await driver.findContent('.test-select-menu li', text).click();
|
||||
await gu.waitForServer();
|
||||
@@ -58,13 +54,11 @@ async function getListItems(col: string) {
|
||||
.findAll(`.test-config-widget-map-list-for-${col} .test-config-widget-ref-select-label`, el => el.getText());
|
||||
}
|
||||
|
||||
// When refreshing, we need to make sure widget repository is enabled once again.
|
||||
async function refresh() {
|
||||
await driver.navigate().refresh();
|
||||
await gu.waitForDocToLoad();
|
||||
// Switch section and enable config
|
||||
await gu.selectSectionByTitle('Table');
|
||||
await driver.executeScript('window.gristConfig.enableWidgetRepository = true;');
|
||||
await gu.selectSectionByTitle('Widget');
|
||||
}
|
||||
|
||||
@@ -130,6 +124,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
let mainSession: gu.Session;
|
||||
gu.bigScreen();
|
||||
|
||||
let oldEnv: EnvironmentSnapshot;
|
||||
|
||||
addToRepl('getOptions', getOptions);
|
||||
|
||||
@@ -137,6 +132,12 @@ describe('CustomWidgetsConfig', function () {
|
||||
if (server.isExternalServer()) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
oldEnv = new EnvironmentSnapshot();
|
||||
// Set to an unused URL so that the client reports that widgets are available.
|
||||
process.env.GRIST_WIDGET_LIST_URL = 'unused';
|
||||
await server.restart();
|
||||
|
||||
// Create simple widget server that serves manifest.json file, some widgets and some error pages.
|
||||
const widgetServer = await serveSomething(app => {
|
||||
app.get('/manifest.json', (_, res) => {
|
||||
@@ -188,25 +189,23 @@ describe('CustomWidgetsConfig', function () {
|
||||
mainSession = await gu.session().login();
|
||||
const doc = await mainSession.tempDoc(cleanup, 'CustomWidget.grist');
|
||||
docId = doc.id;
|
||||
// Make sure widgets are enabled.
|
||||
await driver.executeScript('window.gristConfig.enableWidgetRepository = true;');
|
||||
await gu.toggleSidePanel('right', 'open');
|
||||
await gu.selectSectionByTitle('Widget');
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
await server.testingHooks.setWidgetRepositoryUrl('');
|
||||
oldEnv.restore();
|
||||
await server.restart();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// Before each test, we will switch to Custom Url (to cleanup the widget)
|
||||
// and then back to the Tester widget.
|
||||
if ((await currentWidget()) !== CUSTOM_URL) {
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(CUSTOM_URL);
|
||||
if ((await gu.getCustomWidgetName()) !== CUSTOM_URL) {
|
||||
await gu.setCustomWidget(CUSTOM_URL);
|
||||
}
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(TESTER_WIDGET);
|
||||
await gu.setCustomWidget(TESTER_WIDGET);
|
||||
await widget.waitForFrame();
|
||||
});
|
||||
|
||||
@@ -218,8 +217,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
assert.isFalse(await driver.find('.test-custom-widget-ready').isPresent());
|
||||
|
||||
// Now select the widget that requires a column.
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(REQUIRED_WIDGET);
|
||||
await gu.setCustomWidget(REQUIRED_WIDGET);
|
||||
await gu.acceptAccessRequest();
|
||||
|
||||
// The widget iframe should be covered with a text explaining that the widget is not configured.
|
||||
@@ -251,15 +249,11 @@ describe('CustomWidgetsConfig', function () {
|
||||
});
|
||||
|
||||
it('should hide mappings when there is no good column', async () => {
|
||||
if ((await currentWidget()) !== CUSTOM_URL) {
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(CUSTOM_URL);
|
||||
}
|
||||
await gu.setWidgetUrl(
|
||||
await gu.setCustomWidgetUrl(
|
||||
createConfigUrl({
|
||||
columns: [{name: 'M2', type: 'Date', optional: true}],
|
||||
requiredAccess: 'read table',
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
await widget.waitForFrame();
|
||||
@@ -307,11 +301,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
|
||||
it('should clear optional mapping', async () => {
|
||||
const revert = await gu.begin();
|
||||
if ((await currentWidget()) !== CUSTOM_URL) {
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(CUSTOM_URL);
|
||||
}
|
||||
await gu.setWidgetUrl(
|
||||
await gu.setCustomWidgetUrl(
|
||||
createConfigUrl({
|
||||
columns: [{name: 'M2', type: 'Date', optional: true}],
|
||||
requiredAccess: 'read table',
|
||||
@@ -356,9 +346,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
it('should render columns mapping', async () => {
|
||||
const revert = await gu.begin();
|
||||
assert.isTrue(await driver.find('.test-vfc-visible-fields-select-all').isPresent());
|
||||
await toggleWidgetMenu();
|
||||
// Select widget that has single column configuration.
|
||||
await clickOption(COLUMN_WIDGET);
|
||||
await gu.setCustomWidget(COLUMN_WIDGET);
|
||||
await widget.waitForFrame();
|
||||
await gu.acceptAccessRequest();
|
||||
await widget.waitForPendingRequests();
|
||||
@@ -386,11 +374,9 @@ describe('CustomWidgetsConfig', function () {
|
||||
|
||||
it('should render multiple mappings', async () => {
|
||||
const revert = await gu.begin();
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(CUSTOM_URL);
|
||||
// This is not standard way of creating widgets. The widgets in this test is reading this parameter
|
||||
// and is using it to invoke the ready method.
|
||||
await gu.setWidgetUrl(
|
||||
await gu.setCustomWidgetUrl(
|
||||
createConfigUrl({
|
||||
columns: ['M1', {name: 'M2', optional: true}, {name: 'M3', title: 'T3'}, {name: 'M4', type: 'Text'}],
|
||||
requiredAccess: 'read table',
|
||||
@@ -448,8 +434,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
it('should clear mappings on widget switch', async () => {
|
||||
const revert = await gu.begin();
|
||||
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(COLUMN_WIDGET);
|
||||
await gu.setCustomWidget(COLUMN_WIDGET);
|
||||
await widget.waitForFrame();
|
||||
await gu.acceptAccessRequest();
|
||||
await widget.waitForPendingRequests();
|
||||
@@ -466,8 +451,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
await clickOption('A');
|
||||
|
||||
// Now change to a widget without columns
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(NORMAL_WIDGET);
|
||||
await gu.setCustomWidget(NORMAL_WIDGET);
|
||||
|
||||
// Picker should disappear and column mappings should be visible
|
||||
assert.isTrue(await driver.find('.test-vfc-visible-fields-select-all').isPresent());
|
||||
@@ -481,8 +465,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
{id: 3, A: 'C'},
|
||||
]);
|
||||
// Now go back to the widget with mappings.
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(COLUMN_WIDGET);
|
||||
await gu.setCustomWidget(COLUMN_WIDGET);
|
||||
await widget.waitForFrame();
|
||||
await gu.acceptAccessRequest();
|
||||
await widget.waitForPendingRequests();
|
||||
@@ -494,9 +477,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
|
||||
it('should render multiple options', async () => {
|
||||
const revert = await gu.begin();
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(CUSTOM_URL);
|
||||
await gu.setWidgetUrl(
|
||||
await gu.setCustomWidgetUrl(
|
||||
createConfigUrl({
|
||||
columns: [
|
||||
{name: 'M1', allowMultiple: true, optional: true},
|
||||
@@ -578,9 +559,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
|
||||
it('should support multiple types in mappings', async () => {
|
||||
const revert = await gu.begin();
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(CUSTOM_URL);
|
||||
await gu.setWidgetUrl(
|
||||
await gu.setCustomWidgetUrl(
|
||||
createConfigUrl({
|
||||
columns: [
|
||||
{name: 'M1', type: 'Date,DateTime', optional: true},
|
||||
@@ -639,9 +618,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
|
||||
it('should support strictType setting', async () => {
|
||||
const revert = await gu.begin();
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(CUSTOM_URL);
|
||||
await gu.setWidgetUrl(
|
||||
await gu.setCustomWidgetUrl(
|
||||
createConfigUrl({
|
||||
columns: [
|
||||
{name: 'Any', type: 'Any', strictType: true, optional: true},
|
||||
@@ -683,9 +660,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
|
||||
it('should react to widget options change', async () => {
|
||||
const revert = await gu.begin();
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(CUSTOM_URL);
|
||||
await gu.setWidgetUrl(
|
||||
await gu.setCustomWidgetUrl(
|
||||
createConfigUrl({
|
||||
columns: [
|
||||
{name: 'Choice', type: 'Choice', strictType: true, optional: true},
|
||||
@@ -731,10 +706,8 @@ describe('CustomWidgetsConfig', function () {
|
||||
|
||||
it('should remove mapping when column is deleted', async () => {
|
||||
const revert = await gu.begin();
|
||||
await toggleWidgetMenu();
|
||||
// Prepare mappings for single and multiple columns
|
||||
await clickOption(CUSTOM_URL);
|
||||
await gu.setWidgetUrl(
|
||||
await gu.setCustomWidgetUrl(
|
||||
createConfigUrl({
|
||||
columns: [{name: 'M1', optional: true}, {name: 'M2', allowMultiple: true, optional: true}],
|
||||
requiredAccess: 'read table',
|
||||
@@ -820,10 +793,8 @@ describe('CustomWidgetsConfig', function () {
|
||||
|
||||
it('should remove mapping when column type is changed', async () => {
|
||||
const revert = await gu.begin();
|
||||
await toggleWidgetMenu();
|
||||
// Prepare mappings for single and multiple columns
|
||||
await clickOption(CUSTOM_URL);
|
||||
await gu.setWidgetUrl(
|
||||
await gu.setCustomWidgetUrl(
|
||||
createConfigUrl({
|
||||
columns: [
|
||||
{name: 'M1', type: 'Text', optional: true},
|
||||
@@ -900,10 +871,9 @@ describe('CustomWidgetsConfig', function () {
|
||||
await gu.undo();
|
||||
|
||||
// Add Custom - no section option by default
|
||||
await gu.addNewSection(/Custom/, /Table1/);
|
||||
await gu.addNewSection(/Custom/, /Table1/, {customWidget: /Custom URL/});
|
||||
assert.isFalse(await hasSectionOption());
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(TESTER_WIDGET);
|
||||
await gu.setCustomWidget(TESTER_WIDGET);
|
||||
assert.isTrue(await hasSectionOption());
|
||||
await gu.undo(2);
|
||||
});
|
||||
@@ -1058,30 +1028,19 @@ describe('CustomWidgetsConfig', function () {
|
||||
|
||||
it('should show options action button', async () => {
|
||||
// Select widget without options
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(NORMAL_WIDGET);
|
||||
await gu.setCustomWidget(NORMAL_WIDGET);
|
||||
assert.isFalse(await hasSectionOption());
|
||||
// Select widget with options
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(TESTER_WIDGET);
|
||||
await gu.setCustomWidget(TESTER_WIDGET);
|
||||
assert.isTrue(await hasSectionOption());
|
||||
// Select widget without options
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(NORMAL_WIDGET);
|
||||
await gu.setCustomWidget(NORMAL_WIDGET);
|
||||
assert.isFalse(await hasSectionOption());
|
||||
});
|
||||
|
||||
it('should prompt user for correct access level', async () => {
|
||||
// Select widget without request
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(NORMAL_WIDGET);
|
||||
await widget.waitForFrame();
|
||||
assert.isFalse(await gu.hasAccessPrompt());
|
||||
assert.equal(await gu.widgetAccess(), AccessLevel.none);
|
||||
assert.equal(await widget.access(), AccessLevel.none);
|
||||
// Select widget that requests read access.
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(READ_WIDGET);
|
||||
await gu.setCustomWidget(READ_WIDGET);
|
||||
await widget.waitForFrame();
|
||||
assert.isTrue(await gu.hasAccessPrompt());
|
||||
assert.equal(await gu.widgetAccess(), AccessLevel.none);
|
||||
@@ -1091,8 +1050,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
assert.equal(await gu.widgetAccess(), AccessLevel.read_table);
|
||||
assert.equal(await widget.access(), AccessLevel.read_table);
|
||||
// Select widget that requests full access.
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(FULL_WIDGET);
|
||||
await gu.setCustomWidget(FULL_WIDGET);
|
||||
await widget.waitForFrame();
|
||||
assert.isTrue(await gu.hasAccessPrompt());
|
||||
assert.equal(await gu.widgetAccess(), AccessLevel.none);
|
||||
@@ -1101,7 +1059,7 @@ describe('CustomWidgetsConfig', function () {
|
||||
await widget.waitForPendingRequests();
|
||||
assert.equal(await gu.widgetAccess(), AccessLevel.full);
|
||||
assert.equal(await widget.access(), AccessLevel.full);
|
||||
await gu.undo(5);
|
||||
await gu.undo(4);
|
||||
});
|
||||
|
||||
it('should pass readonly mode to custom widget', async () => {
|
||||
@@ -1265,7 +1223,6 @@ const widget = {
|
||||
* any existing widget state (even if the Custom URL was already selected).
|
||||
*/
|
||||
async resetWidget() {
|
||||
await toggleWidgetMenu();
|
||||
await clickOption(CUSTOM_URL);
|
||||
await gu.setCustomWidget(CUSTOM_URL);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -219,16 +219,10 @@ describe('LinkingBidirectional', function() {
|
||||
});
|
||||
|
||||
it('should support custom filters', async function() {
|
||||
// Add a new custom section with a widget.
|
||||
// Add a new page with a table and custom widget.
|
||||
await gu.addNewPage('Table', 'Classes', {});
|
||||
|
||||
// Rename this section as Data.
|
||||
await gu.renameActiveSection('Data');
|
||||
|
||||
// Add new custom section with a widget.
|
||||
await gu.addNewSection('Custom', 'Classes', { selectBy: 'Data' });
|
||||
|
||||
// Rename this section as Custom.
|
||||
await gu.addNewSection('Custom', 'Classes', {customWidget: /Custom URL/, selectBy: 'Data'});
|
||||
await gu.renameActiveSection('Custom');
|
||||
|
||||
// Make sure it can be used as a filter.
|
||||
|
||||
@@ -33,22 +33,17 @@ describe('RightPanel', function() {
|
||||
await gu.undo();
|
||||
|
||||
// Add a custom section.
|
||||
await gu.addNewSection('Custom', 'Table1');
|
||||
await gu.addNewSection('Custom', 'Table1', { customWidget: /Custom URL/ });
|
||||
assert.isFalse(await gu.isSidePanelOpen('right'));
|
||||
await gu.undo();
|
||||
|
||||
// Add a custom page.
|
||||
await gu.addNewPage('Custom', 'Table1');
|
||||
await gu.addNewPage('Custom', 'Table1', { customWidget: /Custom URL/ });
|
||||
assert.isFalse(await gu.isSidePanelOpen('right'));
|
||||
await gu.undo();
|
||||
|
||||
// Now open the panel on the column tab.
|
||||
const columnTab = async () => {
|
||||
await gu.toggleSidePanel('right', 'open');
|
||||
await driver.find('.test-right-tab-field').click();
|
||||
};
|
||||
|
||||
await columnTab();
|
||||
await gu.openColumnPanel();
|
||||
|
||||
// Add a chart section.
|
||||
await gu.addNewSection('Chart', 'Table1');
|
||||
@@ -56,7 +51,7 @@ describe('RightPanel', function() {
|
||||
assert.isTrue(await driver.find('.test-right-widget-title').isDisplayed());
|
||||
await gu.undo();
|
||||
|
||||
await columnTab();
|
||||
await gu.openColumnPanel();
|
||||
|
||||
// Add a chart page.
|
||||
await gu.addNewPage('Chart', 'Table1');
|
||||
@@ -64,18 +59,18 @@ describe('RightPanel', function() {
|
||||
assert.isTrue(await driver.find('.test-right-widget-title').isDisplayed());
|
||||
await gu.undo();
|
||||
|
||||
await columnTab();
|
||||
await gu.openColumnPanel();
|
||||
|
||||
// Add a custom section.
|
||||
await gu.addNewSection('Custom', 'Table1');
|
||||
await gu.addNewSection('Custom', 'Table1', { customWidget: /Custom URL/ });
|
||||
assert.isTrue(await gu.isSidePanelOpen('right'));
|
||||
assert.isTrue(await driver.find('.test-right-widget-title').isDisplayed());
|
||||
await gu.undo();
|
||||
|
||||
await columnTab();
|
||||
await gu.openColumnPanel();
|
||||
|
||||
// Add a custom page.
|
||||
await gu.addNewPage('Custom', 'Table1');
|
||||
await gu.addNewPage('Custom', 'Table1', { customWidget: /Custom URL/ });
|
||||
assert.isTrue(await gu.isSidePanelOpen('right'));
|
||||
assert.isTrue(await driver.find('.test-right-widget-title').isDisplayed());
|
||||
await gu.undo();
|
||||
|
||||
@@ -100,7 +100,7 @@ describe("SelectBy", function() {
|
||||
|
||||
// Create a page with with charts and custom widget and then check that no linking is offered
|
||||
await gu.addNewPage(/Chart/, /Table1/);
|
||||
await gu.addNewSection(/Custom/, /Table2/);
|
||||
await gu.addNewSection(/Custom/, /Table2/, {customWidget: /Custom URL/});
|
||||
|
||||
// open add widget to page
|
||||
await driver.findWait('.test-dp-add-new', 2000).doClick();
|
||||
|
||||
@@ -100,15 +100,15 @@ describe("ViewLayoutCollapse", function() {
|
||||
|
||||
// Add custom section.
|
||||
await gu.addNewPage('Table', 'Companies');
|
||||
await gu.addNewSection('Custom', 'Companies', { selectBy: 'COMPANIES'});
|
||||
await gu.addNewSection('Custom', 'Companies', {selectBy: 'COMPANIES'});
|
||||
|
||||
// Serve custom widget.
|
||||
const widgetServer = await serveSomething(app => {
|
||||
addStatic(app);
|
||||
});
|
||||
cleanup.addAfterAll(widgetServer.shutdown);
|
||||
await gu.setCustomWidgetUrl(widgetServer.url + '/probe/index.html', {openGallery: false});
|
||||
await gu.openWidgetPanel();
|
||||
await gu.setWidgetUrl(widgetServer.url + '/probe/index.html');
|
||||
await gu.widgetAccess(AccessLevel.full);
|
||||
|
||||
// Collapse it.
|
||||
@@ -139,15 +139,15 @@ describe("ViewLayoutCollapse", function() {
|
||||
|
||||
// Add custom section.
|
||||
await gu.addNewPage('Table', 'Companies');
|
||||
await gu.addNewSection('Custom', 'Companies', { selectBy: 'COMPANIES'});
|
||||
await gu.addNewSection('Custom', 'Companies', {selectBy: 'COMPANIES'});
|
||||
|
||||
// Serve custom widget.
|
||||
const widgetServer = await serveSomething(app => {
|
||||
addStatic(app);
|
||||
});
|
||||
cleanup.addAfterAll(widgetServer.shutdown);
|
||||
await gu.setCustomWidgetUrl(widgetServer.url + '/probe/index.html', {openGallery: false});
|
||||
await gu.openWidgetPanel();
|
||||
await gu.setWidgetUrl(widgetServer.url + '/probe/index.html');
|
||||
await gu.widgetAccess(AccessLevel.full);
|
||||
|
||||
// Collapse it.
|
||||
|
||||
@@ -3242,17 +3242,52 @@ export async function renameActiveTable(name: string) {
|
||||
await waitForServer();
|
||||
}
|
||||
|
||||
export async function setWidgetUrl(url: string) {
|
||||
await driver.find('.test-config-widget-url').click();
|
||||
// First clear textbox.
|
||||
await clearInput();
|
||||
if (url) {
|
||||
await sendKeys(url);
|
||||
export async function getCustomWidgetName() {
|
||||
await openWidgetPanel();
|
||||
return await driver.find('.test-config-widget-open-custom-widget-gallery').getText();
|
||||
}
|
||||
|
||||
export async function getCustomWidgetInfo(info: 'description'|'developer'|'last-updated') {
|
||||
await openWidgetPanel();
|
||||
if (await driver.find('.test-config-widget-show-custom-widget-details').isPresent()) {
|
||||
await driver.find('.test-config-widget-show-custom-widget-details').click();
|
||||
}
|
||||
if (!await driver.find(`.test-config-widget-custom-widget-${info}`).isPresent()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return await driver.find(`.test-config-widget-custom-widget-${info}`).getText();
|
||||
}
|
||||
|
||||
export async function openCustomWidgetGallery() {
|
||||
await openWidgetPanel();
|
||||
await driver.find('.test-config-widget-open-custom-widget-gallery').click();
|
||||
await waitForServer();
|
||||
}
|
||||
|
||||
interface SetWidgetOptions {
|
||||
/** Defaults to `true`. */
|
||||
openGallery?: boolean;
|
||||
}
|
||||
|
||||
export async function setCustomWidgetUrl(url: string, options: SetWidgetOptions = {}) {
|
||||
const {openGallery = true} = options;
|
||||
if (openGallery) { await openCustomWidgetGallery(); }
|
||||
await driver.find('.test-custom-widget-gallery-custom-url').click();
|
||||
await clearInput();
|
||||
if (url) { await sendKeys(url); }
|
||||
await sendKeys(Key.ENTER);
|
||||
await waitForServer();
|
||||
}
|
||||
|
||||
export async function setCustomWidget(content: string|RegExp, options: SetWidgetOptions = {}) {
|
||||
const {openGallery = true} = options;
|
||||
if (openGallery) { await openCustomWidgetGallery(); }
|
||||
await driver.findContent('.test-custom-widget-gallery-widget', content).click();
|
||||
await driver.find('.test-custom-widget-gallery-save').click();
|
||||
await waitForServer();
|
||||
}
|
||||
|
||||
type BehaviorActions = 'Clear and reset' | 'Convert column to data' | 'Clear and make into formula' |
|
||||
'Convert columns to data';
|
||||
/**
|
||||
|
||||
@@ -99,13 +99,14 @@ export class GristWebDriverUtils {
|
||||
tableRe: RegExp|string = '',
|
||||
options: PageWidgetPickerOptions = {}
|
||||
) {
|
||||
const {customWidget, dismissTips, dontAdd, selectBy, summarize, tableName} = options;
|
||||
const driver = this.driver;
|
||||
if (options.dismissTips) { await this.dismissBehavioralPrompts(); }
|
||||
if (dismissTips) { await this.dismissBehavioralPrompts(); }
|
||||
|
||||
// select right type
|
||||
await driver.findContent('.test-wselect-type', typeRe).doClick();
|
||||
|
||||
if (options.dismissTips) { await this.dismissBehavioralPrompts(); }
|
||||
if (dismissTips) { await this.dismissBehavioralPrompts(); }
|
||||
|
||||
if (tableRe) {
|
||||
const tableEl = driver.findContent('.test-wselect-table', tableRe);
|
||||
@@ -118,34 +119,32 @@ export class GristWebDriverUtils {
|
||||
// let's select table
|
||||
await tableEl.click();
|
||||
|
||||
if (options.dismissTips) { await this.dismissBehavioralPrompts(); }
|
||||
if (dismissTips) { await this.dismissBehavioralPrompts(); }
|
||||
|
||||
const pivotEl = tableEl.find('.test-wselect-pivot');
|
||||
if (await pivotEl.isPresent()) {
|
||||
await this.toggleSelectable(pivotEl, Boolean(options.summarize));
|
||||
await this.toggleSelectable(pivotEl, Boolean(summarize));
|
||||
}
|
||||
|
||||
if (options.summarize) {
|
||||
if (summarize) {
|
||||
for (const columnEl of await driver.findAll('.test-wselect-column')) {
|
||||
const label = await columnEl.getText();
|
||||
// TODO: Matching cols with regexp calls for trouble and adds no value. I think function should be
|
||||
// rewritten using string matching only.
|
||||
const goal = Boolean(options.summarize.find(r => label.match(r)));
|
||||
const goal = Boolean(summarize.find(r => label.match(r)));
|
||||
await this.toggleSelectable(columnEl, goal);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.selectBy) {
|
||||
if (selectBy) {
|
||||
// select link
|
||||
await driver.find('.test-wselect-selectby').doClick();
|
||||
await driver.findContent('.test-wselect-selectby option', options.selectBy).doClick();
|
||||
await driver.findContent('.test-wselect-selectby option', selectBy).doClick();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (options.dontAdd) {
|
||||
return;
|
||||
}
|
||||
if (dontAdd) { return; }
|
||||
|
||||
// add the widget
|
||||
await driver.find('.test-wselect-addBtn').doClick();
|
||||
@@ -154,14 +153,20 @@ export class GristWebDriverUtils {
|
||||
const prompts = await driver.findAll(".test-modal-prompt");
|
||||
const prompt = prompts[0];
|
||||
if (prompt) {
|
||||
if (options.tableName) {
|
||||
if (tableName) {
|
||||
await prompt.doClear();
|
||||
await prompt.click();
|
||||
await driver.sendKeys(options.tableName);
|
||||
await driver.sendKeys(tableName);
|
||||
}
|
||||
await driver.find(".test-modal-confirm").click();
|
||||
}
|
||||
|
||||
if (customWidget) {
|
||||
await this.waitForServer();
|
||||
await driver.findContent('.test-custom-widget-gallery-widget-name', customWidget).click();
|
||||
await driver.find('.test-custom-widget-gallery-save').click();
|
||||
}
|
||||
|
||||
await this.waitForServer();
|
||||
}
|
||||
|
||||
@@ -269,4 +274,6 @@ export interface PageWidgetPickerOptions {
|
||||
dontAdd?: boolean;
|
||||
/** If true, dismiss any tooltips that are shown. */
|
||||
dismissTips?: boolean;
|
||||
/** Optional pattern of custom widget name to select in the gallery. */
|
||||
customWidget?: RegExp|string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user