mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Adding testId to the widget iframe once it receives the ready message
Summary: Iframe with custom widget is marked with a test class `test-custom-widget-ready` when it receives the `ready` message from the rendered widget. Test Plan: Added and updated. Existing test should pass. Reviewers: georgegevoian Reviewed By: georgegevoian Subscribers: georgegevoian Differential Revision: https://phab.getgrist.com/D4023
This commit is contained in:
parent
daf7614fd7
commit
58323f5313
@ -7,6 +7,7 @@
|
|||||||
import {GristDoc} from 'app/client/components/GristDoc';
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
import {buildParseOptionsForm, ParseOptionValues} from 'app/client/components/ParseOptions';
|
import {buildParseOptionsForm, ParseOptionValues} from 'app/client/components/ParseOptions';
|
||||||
import {PluginScreen} from 'app/client/components/PluginScreen';
|
import {PluginScreen} from 'app/client/components/PluginScreen';
|
||||||
|
import {makeTestId} from 'app/client/lib/domUtils';
|
||||||
import {FocusLayer} from 'app/client/lib/FocusLayer';
|
import {FocusLayer} from 'app/client/lib/FocusLayer';
|
||||||
import {ImportSourceElement} from 'app/client/lib/ImportSourceElement';
|
import {ImportSourceElement} from 'app/client/lib/ImportSourceElement';
|
||||||
import {makeT} from 'app/client/lib/localization';
|
import {makeT} from 'app/client/lib/localization';
|
||||||
@ -46,7 +47,6 @@ import {byteString, not} from 'app/common/gutil';
|
|||||||
import {FetchUrlOptions, UploadResult} from 'app/common/uploads';
|
import {FetchUrlOptions, UploadResult} from 'app/common/uploads';
|
||||||
import {ParseOptions, ParseOptionSchema} from 'app/plugin/FileParserAPI';
|
import {ParseOptions, ParseOptionSchema} from 'app/plugin/FileParserAPI';
|
||||||
import {
|
import {
|
||||||
BindableValue,
|
|
||||||
Computed,
|
Computed,
|
||||||
Disposable,
|
Disposable,
|
||||||
dom,
|
dom,
|
||||||
@ -65,7 +65,7 @@ import debounce = require('lodash/debounce');
|
|||||||
|
|
||||||
const t = makeT('Importer');
|
const t = makeT('Importer');
|
||||||
// Custom testId that can be appended conditionally.
|
// Custom testId that can be appended conditionally.
|
||||||
const testId = (id: string, obs?: BindableValue<boolean>) => dom.cls('test-importer-' + id, obs ?? true);
|
const testId = makeTestId('test-importer-');
|
||||||
|
|
||||||
|
|
||||||
// We expect a function for creating the preview GridView, to avoid the need to require the
|
// We expect a function for creating the preview GridView, to avoid the need to require the
|
||||||
|
@ -2,6 +2,7 @@ import BaseView from 'app/client/components/BaseView';
|
|||||||
import {GristDoc} from 'app/client/components/GristDoc';
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
import {hooks} from 'app/client/Hooks';
|
import {hooks} from 'app/client/Hooks';
|
||||||
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
|
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
|
||||||
|
import {makeTestId} from 'app/client/lib/domUtils';
|
||||||
import {ColumnRec, ViewSectionRec} from 'app/client/models/DocModel';
|
import {ColumnRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
import {AccessLevel, isSatisfied} from 'app/common/CustomWidget';
|
import {AccessLevel, isSatisfied} from 'app/common/CustomWidget';
|
||||||
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
|
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
|
||||||
@ -16,6 +17,9 @@ import debounce = require('lodash/debounce');
|
|||||||
import isEqual = require('lodash/isEqual');
|
import isEqual = require('lodash/isEqual');
|
||||||
import flatMap = require('lodash/flatMap');
|
import flatMap = require('lodash/flatMap');
|
||||||
|
|
||||||
|
const testId = makeTestId('test-custom-widget-');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains a WidgetFrame and all its components.
|
* This file contains a WidgetFrame and all its components.
|
||||||
*
|
*
|
||||||
@ -62,6 +66,8 @@ export class WidgetFrame extends DisposableWithEvents {
|
|||||||
private _rpc: Rpc;
|
private _rpc: Rpc;
|
||||||
// Created iframe element, used to receive and post messages via Rpc
|
// Created iframe element, used to receive and post messages via Rpc
|
||||||
private _iframe: HTMLIFrameElement | null;
|
private _iframe: HTMLIFrameElement | null;
|
||||||
|
// If widget called ready() method, this will be set to true.
|
||||||
|
private _readyCalled = Observable.create(this, false);
|
||||||
|
|
||||||
constructor(private _options: WidgetFrameOptions) {
|
constructor(private _options: WidgetFrameOptions) {
|
||||||
super();
|
super();
|
||||||
@ -155,10 +161,14 @@ export class WidgetFrame extends DisposableWithEvents {
|
|||||||
const fullUrl = urlWithAccess(this._options.url);
|
const fullUrl = urlWithAccess(this._options.url);
|
||||||
const onElem = this._options.onElem ?? ((el: HTMLIFrameElement) => el);
|
const onElem = this._options.onElem ?? ((el: HTMLIFrameElement) => el);
|
||||||
return onElem(
|
return onElem(
|
||||||
(this._iframe = dom('iframe', dom.cls('clipboard_focus'), dom.cls('custom_view'), {
|
(this._iframe = dom('iframe',
|
||||||
|
dom.cls('clipboard_focus'),
|
||||||
|
dom.cls('custom_view'), {
|
||||||
src: fullUrl,
|
src: fullUrl,
|
||||||
...hooks.iframeAttributes,
|
...hooks.iframeAttributes,
|
||||||
}))
|
},
|
||||||
|
testId('ready', this._readyCalled),
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,6 +187,7 @@ export class WidgetFrame extends DisposableWithEvents {
|
|||||||
}
|
}
|
||||||
if (event.data.mtype === MsgType.Ready) {
|
if (event.data.mtype === MsgType.Ready) {
|
||||||
this.trigger('ready', this);
|
this.trigger('ready', this);
|
||||||
|
this._readyCalled.set(true);
|
||||||
}
|
}
|
||||||
this._rpc.receiveMessage(event.data);
|
this._rpc.receiveMessage(event.data);
|
||||||
}
|
}
|
||||||
@ -513,7 +524,7 @@ export class RecordNotifier extends BaseEventSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies about options position change. Exposed in the API as a onOptions handler.
|
* Notifies about options change. Exposed in the API as a onOptions handler.
|
||||||
*/
|
*/
|
||||||
export class ConfigNotifier extends BaseEventSource {
|
export class ConfigNotifier extends BaseEventSource {
|
||||||
private _currentConfig: Computed<any | null>;
|
private _currentConfig: Computed<any | null>;
|
||||||
|
9
app/client/lib/domUtils.ts
Normal file
9
app/client/lib/domUtils.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import {BindableValue, dom} from 'grainjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version of makeTestId that can be appended conditionally.
|
||||||
|
* TODO: update grainjs typings, as this is already supported there.
|
||||||
|
*/
|
||||||
|
export function makeTestId(prefix: string) {
|
||||||
|
return (id: string, obs?: BindableValue<boolean>) => dom.cls(prefix + id, obs ?? true);
|
||||||
|
}
|
@ -39,6 +39,38 @@ describe('CustomView', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This tests if test id works. Feels counterintuitive to "test the test" but grist-widget repository test suite
|
||||||
|
// depends on this.
|
||||||
|
it('informs about ready called', async () => {
|
||||||
|
// Add a custom inline widget to a new doc.
|
||||||
|
const session = await gu.session().teamSite.login();
|
||||||
|
await session.tempNewDoc(cleanup);
|
||||||
|
await gu.addNewSection('Custom', 'Table1');
|
||||||
|
|
||||||
|
// Create an inline widget that will call ready message.
|
||||||
|
await inFrame(async () => {
|
||||||
|
const customWidget = `
|
||||||
|
<script src="/grist-plugin-api.js"></script>
|
||||||
|
<button onclick="grist.ready()">Ready</button>
|
||||||
|
`;
|
||||||
|
await driver.executeScript("document.write(`" + customWidget + "`);");
|
||||||
|
});
|
||||||
|
|
||||||
|
// We should have a single iframe.
|
||||||
|
assert.equal(await driver.findAll('iframe').then(f => f.length), 1);
|
||||||
|
|
||||||
|
// But without test ready class.
|
||||||
|
assert.isFalse(await driver.find("iframe.test-custom-widget-ready").isPresent());
|
||||||
|
|
||||||
|
// Now invoke ready.
|
||||||
|
await inFrame(async () => {
|
||||||
|
await driver.find('button').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
// And we should have a test ready class.
|
||||||
|
assert.isTrue(await driver.findWait("iframe.test-custom-widget-ready", 100).isDisplayed());
|
||||||
|
});
|
||||||
|
|
||||||
for (const access of ['none', 'read table', 'full'] as const) {
|
for (const access of ['none', 'read table', 'full'] as const) {
|
||||||
|
|
||||||
function withAccess(obj: any, fallback: any) {
|
function withAccess(obj: any, fallback: any) {
|
||||||
@ -478,3 +510,9 @@ describe('CustomView', function() {
|
|||||||
assert.equal(opinions['A'][0], 'do not zap plz');
|
assert.equal(opinions['A'][0], 'do not zap plz');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function inFrame(op: () => Promise<void>) {
|
||||||
|
await driver.switchTo().frame(driver.find("iframe"));
|
||||||
|
await op();
|
||||||
|
await driver.switchTo().defaultContent();
|
||||||
|
}
|
||||||
|
@ -220,14 +220,10 @@ describe('CustomWidgetsConfig', function () {
|
|||||||
|
|
||||||
// Poor man widget rpc. Class that invokes various parts in the tester widget.
|
// Poor man widget rpc. Class that invokes various parts in the tester widget.
|
||||||
class Widget {
|
class Widget {
|
||||||
constructor(public frameSelector = 'iframe') {}
|
constructor() {}
|
||||||
// Wait for a frame.
|
// Wait for a frame.
|
||||||
public async waitForFrame() {
|
public async waitForFrame() {
|
||||||
await driver.wait(() => driver.find(this.frameSelector).isPresent(), 3000);
|
await driver.findWait(`iframe.test-custom-widget-ready`, 1000);
|
||||||
const iframe = driver.find(this.frameSelector);
|
|
||||||
await driver.switchTo().frame(iframe);
|
|
||||||
await driver.wait(async () => (await driver.find('#ready').getText()) === 'ready', 3000);
|
|
||||||
await driver.switchTo().defaultContent();
|
|
||||||
}
|
}
|
||||||
public async content() {
|
public async content() {
|
||||||
return await this._read('body');
|
return await this._read('body');
|
||||||
@ -254,11 +250,11 @@ describe('CustomWidgetsConfig', function () {
|
|||||||
}
|
}
|
||||||
// Wait for frame to close.
|
// Wait for frame to close.
|
||||||
public async waitForClose() {
|
public async waitForClose() {
|
||||||
await driver.wait(async () => !(await driver.find(this.frameSelector).isPresent()), 3000);
|
await driver.wait(async () => !(await driver.find('iframe').isPresent()), 3000);
|
||||||
}
|
}
|
||||||
// Wait for the onOptions event, and return its value.
|
// Wait for the onOptions event, and return its value.
|
||||||
public async onOptions() {
|
public async onOptions() {
|
||||||
const iframe = driver.find(this.frameSelector);
|
const iframe = driver.find('iframe');
|
||||||
await driver.switchTo().frame(iframe);
|
await driver.switchTo().frame(iframe);
|
||||||
// Wait for options to get filled, initially this div is empty,
|
// Wait for options to get filled, initially this div is empty,
|
||||||
// as first message it should get at least null as an options.
|
// as first message it should get at least null as an options.
|
||||||
@ -296,7 +292,7 @@ describe('CustomWidgetsConfig', function () {
|
|||||||
// serialized to the #output div.
|
// serialized to the #output div.
|
||||||
public async invokeOnWidget(name: string, input?: any[]) {
|
public async invokeOnWidget(name: string, input?: any[]) {
|
||||||
// Switch to frame.
|
// Switch to frame.
|
||||||
const iframe = driver.find(this.frameSelector);
|
const iframe = driver.find('iframe');
|
||||||
await driver.switchTo().frame(iframe);
|
await driver.switchTo().frame(iframe);
|
||||||
// Clear input box that holds arguments.
|
// Clear input box that holds arguments.
|
||||||
await driver.find('#input').click();
|
await driver.find('#input').click();
|
||||||
@ -331,7 +327,7 @@ describe('CustomWidgetsConfig', function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _read(selector: string) {
|
private async _read(selector: string) {
|
||||||
const iframe = driver.find(this.frameSelector);
|
const iframe = driver.find('iframe');
|
||||||
await driver.switchTo().frame(iframe);
|
await driver.switchTo().frame(iframe);
|
||||||
const text = await driver.find(selector).getText();
|
const text = await driver.find(selector).getText();
|
||||||
await driver.switchTo().defaultContent();
|
await driver.switchTo().defaultContent();
|
||||||
|
Loading…
Reference in New Issue
Block a user