Commit 44f8354c authored by Eduardo Sanz García's avatar Eduardo Sanz García Committed by Eduardo

Apply suggestions from code review

parent 55a86bb0
...@@ -102,7 +102,8 @@ function resolveAnchor(anchor) { ...@@ -102,7 +102,8 @@ function resolveAnchor(anchor) {
* loads Hypothesis (not all frames will be annotation-enabled). In one frame, * loads Hypothesis (not all frames will be annotation-enabled). In one frame,
* usually the top-level one, there will also be an instance of the `Sidebar` * usually the top-level one, there will also be an instance of the `Sidebar`
* class that shows the sidebar app and surrounding UI. The `Guest` instance in * class that shows the sidebar app and surrounding UI. The `Guest` instance in
* each frame connects to the sidebar when {@link connectToSidebar} is called. * each frame connects to the sidebar as part of its initialization, when
* {@link _connectSidebarEvents} is called.
* *
* The anchoring implementation defaults to a generic one for HTML documents and * The anchoring implementation defaults to a generic one for HTML documents and
* can be overridden to handle different document types. * can be overridden to handle different document types.
...@@ -118,10 +119,14 @@ export default class Guest { ...@@ -118,10 +119,14 @@ export default class Guest {
* @param {EventBus} eventBus - * @param {EventBus} eventBus -
* Enables communication between components sharing the same eventBus * Enables communication between components sharing the same eventBus
* @param {Record<string, any>} [config] * @param {Record<string, any>} [config]
* @param {Window} [hostFrame] -
* Host frame which this guest is associated with. This is expected to be
* an ancestor of the guest frame. It may be same or cross origin.
*/ */
constructor(element, eventBus, config = {}) { constructor(element, eventBus, config = {}, hostFrame = window) {
this.element = element; this.element = element;
this._emitter = eventBus.createEmitter(); this._emitter = eventBus.createEmitter();
this._hostFrame = hostFrame;
this._highlightsVisible = false; this._highlightsVisible = false;
this._isAdderVisible = false; this._isAdderVisible = false;
...@@ -161,9 +166,7 @@ export default class Guest { ...@@ -161,9 +166,7 @@ export default class Guest {
// Set the frame identifier if it's available. // Set the frame identifier if it's available.
// The "top" guest instance will have this as null since it's in a top frame not a sub frame // The "top" guest instance will have this as null since it's in a top frame not a sub frame
/** @type {string|null} */ /** @type {string|null} */
this._frameIdentifier = config.subFrameIdentifier; this._frameIdentifier = config.subFrameIdentifier || null;
this._hostFrame =
config.subFrameIdentifier === null ? window : window.parent;
this._portFinder = new PortFinder({ this._portFinder = new PortFinder({
hostFrame: this._hostFrame, hostFrame: this._hostFrame,
source: 'guest', source: 'guest',
...@@ -182,10 +185,6 @@ export default class Guest { ...@@ -182,10 +185,6 @@ export default class Guest {
this._annotationSync = new AnnotationSync(eventBus, this._bridge); this._annotationSync = new AnnotationSync(eventBus, this._bridge);
this._connectAnnotationSync(); this._connectAnnotationSync();
// Discover and connect to the sidebar frame. All RPC events must be
// registered before creating the channel.
this._connectToSidebar();
// Set up automatic and integration-triggered injection of client into // Set up automatic and integration-triggered injection of client into
// iframes in this frame. // iframes in this frame.
this._hypothesisInjector = new HypothesisInjector(this.element, config); this._hypothesisInjector = new HypothesisInjector(this.element, config);
...@@ -316,7 +315,7 @@ export default class Guest { ...@@ -316,7 +315,7 @@ export default class Guest {
}); });
} }
_connectSidebarEvents() { async _connectSidebarEvents() {
// Handlers for events sent when user hovers or clicks on an annotation card // Handlers for events sent when user hovers or clicks on an annotation card
// in the sidebar. // in the sidebar.
this._bridge.on('focusAnnotations', (tags = []) => { this._bridge.on('focusAnnotations', (tags = []) => {
...@@ -367,12 +366,9 @@ export default class Guest { ...@@ -367,12 +366,9 @@ export default class Guest {
this._bridge.on('setHighlightsVisible', showHighlights => { this._bridge.on('setHighlightsVisible', showHighlights => {
this.setHighlightsVisible(showHighlights); this.setHighlightsVisible(showHighlights);
}); });
}
/** // Discover and connect to the sidebar frame. All RPC events must be
* Attempt to connect to the sidebar frame. // registered before creating the channel.
*/
async _connectToSidebar() {
const sidebarPort = await this._portFinder.discover('sidebar'); const sidebarPort = await this._portFinder.discover('sidebar');
this._bridge.createChannel(sidebarPort); this._bridge.createChannel(sidebarPort);
} }
......
/**
* @typedef {import('../types/annotator').HypothesisWindow} HypothesisWindow
*/
// Load polyfill for :focus-visible pseudo-class. // Load polyfill for :focus-visible pseudo-class.
import 'focus-visible'; import 'focus-visible';
...@@ -13,6 +9,7 @@ import { registerIcons } from '@hypothesis/frontend-shared'; ...@@ -13,6 +9,7 @@ import { registerIcons } from '@hypothesis/frontend-shared';
import iconSet from './icons'; import iconSet from './icons';
registerIcons(iconSet); registerIcons(iconSet);
import { PortProvider } from '../shared/port-provider';
import { getConfig } from './config/index'; import { getConfig } from './config/index';
import Guest from './guest'; import Guest from './guest';
import Notebook from './notebook'; import Notebook from './notebook';
...@@ -41,23 +38,36 @@ const sidebarLinkElement = /** @type {HTMLLinkElement} */ ( ...@@ -41,23 +38,36 @@ const sidebarLinkElement = /** @type {HTMLLinkElement} */ (
*/ */
function init() { function init() {
const annotatorConfig = getConfig('annotator'); const annotatorConfig = getConfig('annotator');
const isHostFrame = annotatorConfig.subFrameIdentifier === null;
const hostFrame = annotatorConfig.subFrameIdentifier ? window.parent : window;
// Create the guest that handles creating annotations and displaying highlights. // Create the guest that handles creating annotations and displaying highlights.
const eventBus = new EventBus(); const eventBus = new EventBus();
const guest = new Guest(document.body, eventBus, annotatorConfig); const guest = new Guest(document.body, eventBus, annotatorConfig, hostFrame);
let sidebar; let sidebar;
let notebook; let notebook;
if (isHostFrame) { let portProvider;
sidebar = new Sidebar(document.body, eventBus, guest, getConfig('sidebar')); if (hostFrame === window) {
const sidebarConfig = getConfig('sidebar');
const hypothesisAppsOrigin = new URL(sidebarConfig.sidebarAppUrl).origin;
portProvider = new PortProvider(hypothesisAppsOrigin);
// Clear `annotations` value from the notebook's config to prevent direct-linked sidebar = new Sidebar(
// annotations from filtering the threads. document.body,
eventBus,
portProvider.hostPortFor('sidebar'),
guest,
sidebarConfig
);
notebook = new Notebook(document.body, eventBus, getConfig('notebook')); notebook = new Notebook(document.body, eventBus, getConfig('notebook'));
portProvider.listen();
} }
sidebarLinkElement.addEventListener('destroy', () => { sidebarLinkElement.addEventListener('destroy', () => {
portProvider?.destroy();
sidebar?.destroy(); sidebar?.destroy();
notebook?.destroy(); notebook?.destroy();
guest.destroy(); guest.destroy();
......
...@@ -2,7 +2,6 @@ import Hammer from 'hammerjs'; ...@@ -2,7 +2,6 @@ import Hammer from 'hammerjs';
import { Bridge } from '../shared/bridge'; import { Bridge } from '../shared/bridge';
import { ListenerCollection } from '../shared/listener-collection'; import { ListenerCollection } from '../shared/listener-collection';
import { PortProvider } from '../shared/port-provider';
import { annotationCounts } from './annotation-counts'; import { annotationCounts } from './annotation-counts';
import BucketBar from './bucket-bar'; import BucketBar from './bucket-bar';
...@@ -57,16 +56,18 @@ export default class Sidebar { ...@@ -57,16 +56,18 @@ export default class Sidebar {
* @param {HTMLElement} element * @param {HTMLElement} element
* @param {import('./util/emitter').EventBus} eventBus - * @param {import('./util/emitter').EventBus} eventBus -
* Enables communication between components sharing the same eventBus * Enables communication between components sharing the same eventBus
* @param {MessagePort} hostPort -
* Host port for the host-sidebar communication channel. The sidebar app will
* request its side of the channel when it starts.
* @param {Guest} guest - * @param {Guest} guest -
* The `Guest` instance for the current frame. It is currently assumed that * The `Guest` instance for the current frame. It is currently assumed that
* it is always possible to annotate in the frame where the sidebar is * it is always possible to annotate in the frame where the sidebar is
* displayed. * displayed.
* @param {Record<string, any>} [config] * @param {Record<string, any>} [config]
*/ */
constructor(element, eventBus, guest, config = {}) { constructor(element, eventBus, hostPort, guest, config = {}) {
this._emitter = eventBus.createEmitter(); this._emitter = eventBus.createEmitter();
const hypothesisAppsOrigin = new URL(config.sidebarAppUrl).origin; this._hostPort = hostPort;
this._portProvider = new PortProvider(hypothesisAppsOrigin);
/** /**
* Channel for host-sidebar communication. * Channel for host-sidebar communication.
...@@ -198,8 +199,7 @@ export default class Sidebar { ...@@ -198,8 +199,7 @@ export default class Sidebar {
}); });
// Create channel *after* all bridge events are registered with the `on` method. // Create channel *after* all bridge events are registered with the `on` method.
this._sidebarRPC.createChannel(this._portProvider.sidebarPort); this._sidebarRPC.createChannel(this._hostPort);
this._portProvider.listen();
// Notify sidebar when a guest is unloaded. This message is routed via // Notify sidebar when a guest is unloaded. This message is routed via
// the host frame because in Safari guest frames are unable to send messages // the host frame because in Safari guest frames are unable to send messages
...@@ -214,7 +214,6 @@ export default class Sidebar { ...@@ -214,7 +214,6 @@ export default class Sidebar {
} }
destroy() { destroy() {
this._portProvider.destroy();
this.bucketBar?.destroy(); this.bucketBar?.destroy();
this._listeners.removeAll(); this._listeners.removeAll();
this._hammerManager?.destroy(); this._hammerManager?.destroy();
......
...@@ -33,8 +33,9 @@ describe('Guest', () => { ...@@ -33,8 +33,9 @@ describe('Guest', () => {
let eventBus; let eventBus;
let guests; let guests;
let highlighter; let highlighter;
let rangeUtil; let hostFrame;
let notifySelectionChanged; let notifySelectionChanged;
let rangeUtil;
let FakeAnnotationSync; let FakeAnnotationSync;
let fakeAnnotationSync; let fakeAnnotationSync;
...@@ -47,7 +48,7 @@ describe('Guest', () => { ...@@ -47,7 +48,7 @@ describe('Guest', () => {
const createGuest = (config = {}) => { const createGuest = (config = {}) => {
const element = document.createElement('div'); const element = document.createElement('div');
eventBus = new EventBus(); eventBus = new EventBus();
const guest = new Guest(element, eventBus, config); const guest = new Guest(element, eventBus, config, hostFrame);
guests.push(guest); guests.push(guest);
return guest; return guest;
}; };
...@@ -62,12 +63,15 @@ describe('Guest', () => { ...@@ -62,12 +63,15 @@ describe('Guest', () => {
setHighlightsFocused: sinon.stub(), setHighlightsFocused: sinon.stub(),
setHighlightsVisible: sinon.stub(), setHighlightsVisible: sinon.stub(),
}; };
hostFrame = {
postMessage: sinon.stub(),
};
notifySelectionChanged = null;
rangeUtil = { rangeUtil = {
itemsForRange: sinon.stub().returns([]), itemsForRange: sinon.stub().returns([]),
isSelectionBackwards: sinon.stub(), isSelectionBackwards: sinon.stub(),
selectionFocusRect: sinon.stub(), selectionFocusRect: sinon.stub(),
}; };
notifySelectionChanged = null;
FakeAdder.instance = null; FakeAdder.instance = null;
...@@ -117,8 +121,6 @@ describe('Guest', () => { ...@@ -117,8 +121,6 @@ describe('Guest', () => {
} }
} }
sandbox.stub(window.parent, 'postMessage');
$imports.$mock({ $imports.$mock({
'../shared/bridge': { Bridge: sinon.stub().returns(fakeBridge) }, '../shared/bridge': { Bridge: sinon.stub().returns(fakeBridge) },
'../shared/port-finder': { '../shared/port-finder': {
...@@ -1136,7 +1138,7 @@ describe('Guest', () => { ...@@ -1136,7 +1138,7 @@ describe('Guest', () => {
guest.destroy(); guest.destroy();
assert.calledWith( assert.calledWith(
window.parent.postMessage, hostFrame.postMessage,
{ {
type: 'hypothesisGuestUnloaded', type: 'hypothesisGuestUnloaded',
frameIdentifier: 'frame-id', frameIdentifier: 'frame-id',
...@@ -1152,7 +1154,7 @@ describe('Guest', () => { ...@@ -1152,7 +1154,7 @@ describe('Guest', () => {
window.dispatchEvent(new Event('unload')); window.dispatchEvent(new Event('unload'));
assert.calledWith( assert.calledWith(
window.parent.postMessage, hostFrame.postMessage,
{ {
type: 'hypothesisGuestUnloaded', type: 'hypothesisGuestUnloaded',
frameIdentifier: 'frame-id', frameIdentifier: 'frame-id',
...@@ -1174,10 +1176,7 @@ describe('Guest', () => { ...@@ -1174,10 +1176,7 @@ describe('Guest', () => {
await delay(0); await delay(0);
assert.calledWith( assert.calledWith(fakeBridge.createChannel, port1);
fakeBridge.createChannel,
sinon.match.instanceOf(MessagePort)
);
}); });
describe('#contentContainer', () => { describe('#contentContainer', () => {
......
// Tests that the expected parts of the page are highlighted when annotations // Tests that the expected parts of the page are highlighted when annotations
// with various combinations of selector are anchored. // with various combinations of selector are anchored.
import Guest, { $imports } from '../../guest'; import Guest, { $imports as guestImports } from '../../guest';
import { EventBus } from '../../util/emitter'; import { EventBus } from '../../util/emitter';
import testPageHTML from './test-page.html'; import testPageHTML from './test-page.html';
...@@ -54,7 +54,7 @@ describe('anchoring', () => { ...@@ -54,7 +54,7 @@ describe('anchoring', () => {
discover: sinon.stub().resolves(new MessageChannel().port1), discover: sinon.stub().resolves(new MessageChannel().port1),
destroy: sinon.stub(), destroy: sinon.stub(),
}; };
$imports.$mock({ guestImports.$mock({
'../shared/port-finder': { '../shared/port-finder': {
PortFinder: sinon.stub().returns(fakePortFinder), PortFinder: sinon.stub().returns(fakePortFinder),
}, },
...@@ -66,7 +66,7 @@ describe('anchoring', () => { ...@@ -66,7 +66,7 @@ describe('anchoring', () => {
guest.destroy(); guest.destroy();
container.remove(); container.remove();
console.warn.restore(); console.warn.restore();
$imports.$restore(); guestImports.$restore();
}); });
[ [
......
...@@ -7,7 +7,6 @@ const EXTERNAL_CONTAINER_SELECTOR = 'test-external-container'; ...@@ -7,7 +7,6 @@ const EXTERNAL_CONTAINER_SELECTOR = 'test-external-container';
describe('Sidebar', () => { describe('Sidebar', () => {
const sandbox = sinon.createSandbox(); const sandbox = sinon.createSandbox();
let fakeGuest;
// Containers and Sidebar instances created by current test. // Containers and Sidebar instances created by current test.
let containers; let containers;
...@@ -16,7 +15,7 @@ describe('Sidebar', () => { ...@@ -16,7 +15,7 @@ describe('Sidebar', () => {
let fakeBridge; let fakeBridge;
let FakeBucketBar; let FakeBucketBar;
let fakeBucketBar; let fakeBucketBar;
let fakePortProvider; let fakeGuest;
let FakeToolbarController; let FakeToolbarController;
let fakeToolbar; let fakeToolbar;
...@@ -41,7 +40,7 @@ describe('Sidebar', () => { ...@@ -41,7 +40,7 @@ describe('Sidebar', () => {
const createSidebar = (config = {}) => { const createSidebar = (config = {}) => {
config = { config = {
// Dummy sidebar app. // Dummy sidebar app.
sidebarAppUrl: 'https://hyp.is/base/annotator/test/empty.html', sidebarAppUrl: '/base/annotator/test/empty.html',
...config, ...config,
}; };
const container = document.createElement('div'); const container = document.createElement('div');
...@@ -49,7 +48,8 @@ describe('Sidebar', () => { ...@@ -49,7 +48,8 @@ describe('Sidebar', () => {
containers.push(container); containers.push(container);
const eventBus = new EventBus(); const eventBus = new EventBus();
const sidebar = new Sidebar(container, eventBus, fakeGuest, config); const { port1 } = new MessageChannel();
const sidebar = new Sidebar(container, eventBus, port1, fakeGuest, config);
sidebars.push(sidebar); sidebars.push(sidebar);
return sidebar; return sidebar;
...@@ -95,12 +95,6 @@ describe('Sidebar', () => { ...@@ -95,12 +95,6 @@ describe('Sidebar', () => {
} }
fakeGuest = new FakeGuest(); fakeGuest = new FakeGuest();
fakePortProvider = {
listen: sinon.stub(),
sidebarPort: sinon.stub(),
destroy: sinon.stub(),
};
fakeToolbar = { fakeToolbar = {
getWidth: sinon.stub().returns(100), getWidth: sinon.stub().returns(100),
useMinimalControls: false, useMinimalControls: false,
...@@ -113,9 +107,6 @@ describe('Sidebar', () => { ...@@ -113,9 +107,6 @@ describe('Sidebar', () => {
$imports.$mock({ $imports.$mock({
'../shared/bridge': { Bridge: sinon.stub().returns(fakeBridge) }, '../shared/bridge': { Bridge: sinon.stub().returns(fakeBridge) },
'../shared/port-provider': {
PortProvider: sinon.stub().returns(fakePortProvider),
},
'./bucket-bar': { default: FakeBucketBar }, './bucket-bar': { default: FakeBucketBar },
'./toolbar': { './toolbar': {
ToolbarController: FakeToolbarController, ToolbarController: FakeToolbarController,
...@@ -191,11 +182,14 @@ describe('Sidebar', () => { ...@@ -191,11 +182,14 @@ describe('Sidebar', () => {
} }
it('creates sidebar iframe and passes configuration to it', () => { it('creates sidebar iframe and passes configuration to it', () => {
const appURL = 'https://hyp.is/base/annotator/test/empty.html'; const appURL = new URL(
'/base/annotator/test/empty.html',
window.location.href
);
const sidebar = createSidebar({ annotations: '1234' }); const sidebar = createSidebar({ annotations: '1234' });
assert.equal( assert.equal(
getConfigString(sidebar), getConfigString(sidebar),
`${appURL}${configFragment({ annotations: '1234' })}` appURL + configFragment({ annotations: '1234' })
); );
}); });
......
import { ListenerCollection } from './listener-collection'; import { ListenerCollection } from './listener-collection';
import { isMessageEqual } from './port-util'; import { isMessageEqual } from './port-util';
const MAX_WAIT_FOR_PORT = 1000 * 5; const MAX_WAIT_FOR_PORT = 1000 * 10;
const POLLING_INTERVAL_FOR_PORT = 250; const POLLING_INTERVAL_FOR_PORT = 250;
/** /**
......
...@@ -164,14 +164,15 @@ export class PortProvider { ...@@ -164,14 +164,15 @@ export class PortProvider {
} }
if (channel === 'guest-host' && message.frame1 === 'guest') { if (channel === 'guest-host' && message.frame1 === 'guest') {
this._emitter.emit('hostPortRequest', message.frame1, port2); this._emitter.emit('frameConnected', message.frame1, port2);
} }
} }
/** /**
* @param {'hostPortRequest'} eventName * @param {'frameConnected'} eventName
* @param {(source: 'guest', port: MessagePort) => void} handler - this handler * @param {(source: 'guest', port: MessagePort) => void} handler - this handler
* fires when a request for the 'guest-host' channel is listened. * fires when a request for the host frame has been granted. Currently, only
* the 'guest-host' channel triggers this listener.
*/ */
on(eventName, handler) { on(eventName, handler) {
this._emitter.on(eventName, handler); this._emitter.on(eventName, handler);
...@@ -179,8 +180,11 @@ export class PortProvider { ...@@ -179,8 +180,11 @@ export class PortProvider {
/** /**
* Returns the `host` port from the `sidebar-host` channel. * Returns the `host` port from the `sidebar-host` channel.
*
* @param {'sidebar'} _targetFrame
*/ */
get sidebarPort() { // eslint-disable-next-line no-unused-vars
hostPortFor(_targetFrame) {
return this._sidebarHostChannel.port2; return this._sidebarHostChannel.port2;
} }
......
import { delay } from '../../test-util/wait'; import { delay } from '../../test-util/wait';
import { PortFinder } from '../port-finder'; import { PortFinder } from '../port-finder';
const MAX_WAIT_FOR_PORT = 1000 * 5; const MAX_WAIT_FOR_PORT = 1000 * 10;
describe('PortFinder', () => { describe('PortFinder', () => {
const frame1 = 'guest'; const frame1 = 'guest';
...@@ -122,7 +122,7 @@ describe('PortFinder', () => { ...@@ -122,7 +122,7 @@ describe('PortFinder', () => {
clock.restore(); clock.restore();
} }
assert.callCount(window.postMessage, 21); assert.callCount(window.postMessage, 41);
assert.alwaysCalledWithExactly( assert.alwaysCalledWithExactly(
window.postMessage, window.postMessage,
{ frame1, frame2: target, type: 'request' }, { frame1, frame2: target, type: 'request' },
......
...@@ -46,9 +46,9 @@ describe('PortProvider', () => { ...@@ -46,9 +46,9 @@ describe('PortProvider', () => {
}); });
}); });
describe('#sidebarPort', () => { describe('#hostPortFor', () => {
it('returns the `host` port of the `sidebar-host` channel', () => { it('returns the `host` port of the `sidebar-host` channel', () => {
assert.instanceOf(portProvider.sidebarPort, MessagePort); assert.instanceOf(portProvider.hostPortFor('sidebar'), MessagePort);
}); });
}); });
...@@ -209,7 +209,7 @@ describe('PortProvider', () => { ...@@ -209,7 +209,7 @@ describe('PortProvider', () => {
it('sends the counterpart port via the listener', async () => { it('sends the counterpart port via the listener', async () => {
portProvider.listen(); portProvider.listen();
const handler = sinon.stub(); const handler = sinon.stub();
portProvider.on('hostPortRequest', handler); portProvider.on('frameConnected', handler);
const data = { const data = {
frame1: 'guest', frame1: 'guest',
frame2: 'host', frame2: 'host',
......
...@@ -285,12 +285,11 @@ export class FrameSyncService { ...@@ -285,12 +285,11 @@ export class FrameSyncService {
this._guestRPC.call('setHighlightsVisible', visible); this._guestRPC.call('setHighlightsVisible', visible);
}); });
// Create channel for sidebar-host communication and send port to host. // Create channel for sidebar-host communication.
const hostPort = await this._portFinder.discover('host'); const hostPort = await this._portFinder.discover('host');
this._hostRPC.createChannel(hostPort); this._hostRPC.createChannel(hostPort);
// Create channel for guest-sidebar communication // Listen for guests connecting to the sidebar.
hostPort.start();
this._listeners.add(hostPort, 'message', event => { this._listeners.add(hostPort, 'message', event => {
const { data, ports } = /** @type {MessageEvent} */ (event); const { data, ports } = /** @type {MessageEvent} */ (event);
if ( if (
......
...@@ -61,9 +61,11 @@ describe('FrameSyncService', () => { ...@@ -61,9 +61,11 @@ describe('FrameSyncService', () => {
let fakeBridges; let fakeBridges;
let fakePortFinder; let fakePortFinder;
let fakeStore; let fakeStore;
let frameSync;
let fakeWindow; let fakeWindow;
const { port1: hostPort, port2: sidebarPort } = new MessageChannel();
let frameSync;
let hostPort;
let sidebarPort;
beforeEach(() => { beforeEach(() => {
fakeAnnotationsService = { create: sinon.stub() }; fakeAnnotationsService = { create: sinon.stub() };
...@@ -84,6 +86,11 @@ describe('FrameSyncService', () => { ...@@ -84,6 +86,11 @@ describe('FrameSyncService', () => {
return bridge; return bridge;
}); });
const { port1, port2 } = new MessageChannel();
hostPort = port1;
sidebarPort = port2;
sidebarPort.start();
fakePortFinder = { fakePortFinder = {
destroy: sinon.stub(), destroy: sinon.stub(),
discover: sinon.stub().resolves(sidebarPort), discover: sinon.stub().resolves(sidebarPort),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment