teletype-client-modified
Version:
I have modified the library developed by Atom to make it compatible with non-browser facility. I am using it only for experimentation purposes. I will remove this package post my experiment and raise a PR at Atom to finally merge this package so that this
1,060 lines (896 loc) • 57.4 kB
JavaScript
require('./setup')
const assert = require('assert')
const deepEqual = require('deep-equal')
const FakeBufferDelegate = require('./helpers/fake-buffer-delegate')
const FakeEditorDelegate = require('./helpers/fake-editor-delegate')
const FakePortalDelegate = require('./helpers/fake-portal-delegate')
const condition = require('./helpers/condition')
const timeout = require('./helpers/timeout')
const {TeletypeClient, FollowState, Errors} = require('..')
const RestGateway = require('../lib/rest-gateway')
const PusherPubSubGateway = require('../lib/pusher-pub-sub-gateway')
const {startTestServer} = require('@atom/teletype-server')
let testEpoch = 0
suite('Client Integration', () => {
let server, portals
suiteSetup(async () => {
const params = {databaseURL: process.env.TEST_DATABASE_URL}
// Uncomment and provide credentials to test against Pusher.
// params.pusherCredentials = {
// appId: '123',
// key: '123',
// secret: '123'
// }
server = await startTestServer(params)
})
suiteTeardown(() => {
return server.stop()
})
setup(() => {
portals = []
return server.reset()
})
teardown(async () => {
for (const portal of portals) {
portal.dispose()
portal.peerPool.disconnect()
}
testEpoch++
})
test('sharing a portal and performing basic collaboration with a guest', async () => {
const host = await buildClient()
const guest = await buildClient()
const hostPortal = await host.createPortal()
let hostSetTextCallCount = 0
const hostBufferDelegate = new FakeBufferDelegate('hello world', {didSetText: () => hostSetTextCallCount++})
const hostBufferProxy = await hostPortal.createBufferProxy({uri: 'uri-1', text: hostBufferDelegate.text})
hostBufferProxy.setDelegate(hostBufferDelegate)
hostBufferProxy.setTextInRange(...hostBufferDelegate.insert({row: 0, column: 11}, '!'))
assert.equal(hostSetTextCallCount, 0)
const hostEditorProxy = await hostPortal.createEditorProxy({
bufferProxy: hostBufferProxy,
selections: {
1: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 5}}},
2: {range: {start: {row: 0, column: 6}, end: {row: 0, column: 11}}}
}
})
const hostEditorDelegate = new FakeEditorDelegate()
hostEditorProxy.setDelegate(hostEditorDelegate)
assert(!hostEditorDelegate.getSelectionsForSiteId(1))
hostPortal.activateEditorProxy(hostEditorProxy)
const guestPortalDelegate = new FakePortalDelegate()
const guestPortal = await guest.joinPortal(hostPortal.id)
await guestPortal.setDelegate(guestPortalDelegate)
const guestEditorProxy = guestPortalDelegate.getTetherEditorProxy()
const guestEditorDelegate = new FakeEditorDelegate()
guestEditorProxy.setDelegate(guestEditorDelegate)
const guestBufferProxy = guestEditorProxy.bufferProxy
const guestBufferDelegate = new FakeBufferDelegate()
guestBufferProxy.setDelegate(guestBufferDelegate)
assert.deepEqual(guestEditorDelegate.getSelectionsForSiteId(1), {
1: {
range: {start: {row: 0, column: 0}, end: {row: 0, column: 5}},
exclusive: false,
reversed: false,
tailed: true
},
2: {
range: {start: {row: 0, column: 6}, end: {row: 0, column: 11}},
exclusive: false,
reversed: false,
tailed: true
}
})
assert.equal(guestBufferProxy.uri, 'uri-1')
assert.equal(guestBufferDelegate.getText(), 'hello world!')
hostBufferProxy.setTextInRange(...hostBufferDelegate.insert({row: 0, column: 5}, ' cruel'))
guestBufferProxy.setTextInRange(...guestBufferDelegate.delete({row: 0, column: 0}, {row: 0, column: 5}))
guestBufferProxy.setTextInRange(...guestBufferDelegate.insert({row: 0, column: 0}, 'goodbye'))
await condition(() => hostBufferDelegate.text === 'goodbye cruel world!')
await condition(() => guestBufferDelegate.text === 'goodbye cruel world!')
hostEditorProxy.updateSelections({
1: {
range: {start: {row: 0, column: 6}, end: {row: 0, column: 11}}
},
2: null
})
guestEditorProxy.updateSelections({
1: {
range: {start: {row: 0, column: 2}, end: {row: 0, column: 4}}
},
2: {
range: {start: {row: 0, column: 6}, end: {row: 0, column: 8}}
}
})
const expectedGuestSelectionsOnHost = {
1: {
range: {start: {row: 0, column: 2}, end: {row: 0, column: 4}},
exclusive: false,
reversed: false,
tailed: true
},
2: {
range: {start: {row: 0, column: 6}, end: {row: 0, column: 8}},
exclusive: false,
reversed: false,
tailed: true
}
}
const expectedHostSelectionsOnGuest = {
1: {
range: {start: {row: 0, column: 6}, end: {row: 0, column: 11}},
exclusive: false,
reversed: false,
tailed: true
}
}
await condition(() => {
return (
deepEqual(guestEditorDelegate.getSelectionsForSiteId(1), expectedHostSelectionsOnGuest) &&
deepEqual(hostEditorDelegate.getSelectionsForSiteId(2), expectedGuestSelectionsOnHost)
)
})
})
test('adding and removing editor proxies', async () => {
const host = await buildClient()
const hostPortal = await host.createPortal()
const guest1 = await buildClient()
const guest1Portal = await guest1.joinPortal(hostPortal.id)
const guest1PortalDelegate = new FakePortalDelegate()
guest1Portal.setDelegate(guest1PortalDelegate)
assert.equal(guest1Portal.getEditorProxiesMetadata().length, 0)
const hostBufferProxy1 = await hostPortal.createBufferProxy({uri: 'buffer-a', text: ''})
const hostEditorProxy1 = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy1})
await condition(() => guest1Portal.getEditorProxiesMetadata().length === 1)
assert.equal(guest1PortalDelegate.editorProxiesChangeEventCount, 1)
const hostBufferProxy2 = await hostPortal.createBufferProxy({uri: 'buffer-b', text: ''})
const hostEditorProxy2 = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy2})
hostPortal.activateEditorProxy(hostEditorProxy2)
await condition(() => guest1Portal.getEditorProxiesMetadata().length === 2)
assert.equal(guest1PortalDelegate.editorProxiesChangeEventCount, 2)
const guest2 = await buildClient()
const guest2Portal = await guest2.joinPortal(hostPortal.id)
const guest2PortalDelegate = new FakePortalDelegate()
guest2Portal.setDelegate(guest2PortalDelegate)
assert.equal(guest2Portal.getEditorProxiesMetadata().length, 2)
assert.equal(guest2PortalDelegate.editorProxiesChangeEventCount, 0)
hostEditorProxy1.dispose()
await condition(() => (
guest1Portal.getEditorProxiesMetadata().length === 1 &&
guest2Portal.getEditorProxiesMetadata().length === 1
))
assert.equal(guest1PortalDelegate.editorProxiesChangeEventCount, 3)
assert.equal(guest2PortalDelegate.editorProxiesChangeEventCount, 1)
hostEditorProxy2.dispose()
hostPortal.activateEditorProxy(null)
await condition(() => (
guest1Portal.getEditorProxiesMetadata().length === 0 &&
guest2Portal.getEditorProxiesMetadata().length === 0
))
assert.equal(guest1PortalDelegate.editorProxiesChangeEventCount, 4)
assert.equal(guest2PortalDelegate.editorProxiesChangeEventCount, 2)
const hostBufferProxy3 = await hostPortal.createBufferProxy({uri: 'buffer-c', text: ''})
const hostEditorProxy3 = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy3})
await condition(() => (
guest1Portal.getEditorProxiesMetadata().length === 1 &&
guest2Portal.getEditorProxiesMetadata().length === 1
))
assert.equal(guest1PortalDelegate.editorProxiesChangeEventCount, 5)
assert.equal(guest2PortalDelegate.editorProxiesChangeEventCount, 3)
const guest3 = await buildClient()
const guest3Portal = await guest3.joinPortal(hostPortal.id)
assert.equal(guest3Portal.getEditorProxiesMetadata().length, 1)
})
suite('tethering to other participants', () => {
test('extending, retracting, and disconnecting when collaborating across multiple editors', async () => {
const host = await buildClient()
const guest1 = await buildClient()
const hostPortal = await host.createPortal()
const hostPortalDelegate = new FakePortalDelegate()
hostPortal.setDelegate(hostPortalDelegate)
const hostBufferProxy1 = await hostPortal.createBufferProxy({uri: 'buffer-1', text: ('x'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy1 = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy1})
hostPortal.activateEditorProxy(hostEditorProxy1)
const guest1PortalDelegate = new FakePortalDelegate()
const guest1Portal = await guest1.joinPortal(hostPortal.id)
await guest1Portal.setDelegate(guest1PortalDelegate)
assert.equal(guest1Portal.resolveFollowState(), FollowState.RETRACTED)
// Extend the tether when the follower performs an action
const guest1EditorProxy1 = guest1PortalDelegate.getTetherEditorProxy()
guest1EditorProxy1.bufferProxy.setTextInRange({row: 1, column: 1}, {row: 1, column: 1}, 'X')
await condition(() => guest1Portal.resolveFollowState() === FollowState.EXTENDED)
// Retract an extended tether if leader moves to a different editor and
// the tether disconnect window has elapsed since the last action taken by
// by the follower
await timeout(guest1Portal.tetherDisconnectWindow)
const hostBufferProxy2 = await hostPortal.createBufferProxy({uri: 'buffer-2', text: ('y'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy2 = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy2})
hostPortal.activateEditorProxy(hostEditorProxy2)
await condition(() => (
guest1Portal.resolveFollowState() === FollowState.RETRACTED &&
guest1PortalDelegate.getTetherBufferProxyURI() === 'buffer-2'
))
// Disconnect an extended tether if leader moves to a different editor
// within the disconnect window.
const guest1EditorProxy2 = guest1PortalDelegate.getTetherEditorProxy()
guest1EditorProxy2.updateSelections({
1: {range: range([2, 1], [2, 1])}
}, {initialUpdate: true})
guest1EditorProxy2.bufferProxy.setTextInRange({row: 1, column: 1}, {row: 1, column: 1}, 'X')
await condition(() => guest1Portal.resolveFollowState() === FollowState.EXTENDED)
hostPortal.activateEditorProxy(hostEditorProxy1)
await condition(() => (
guest1Portal.resolveFollowState() === FollowState.DISCONNECTED &&
guest1PortalDelegate.getTetherBufferProxyURI() === 'buffer-2'
))
// New guests can start following existing participants.
const guest2 = await buildClient()
const guest2PortalDelegate = new FakePortalDelegate()
const guest2Portal = await guest2.joinPortal(hostPortal.id)
await guest2Portal.setDelegate(guest2PortalDelegate)
guest2Portal.follow(guest1Portal.siteId)
assert.equal(guest2PortalDelegate.getTetherBufferProxyURI(), 'buffer-2')
assert.deepEqual(guest2PortalDelegate.getTetherPosition(), {row: 2, column: 1})
// When you start to follow a participant, you go to their position in
// their active editor
hostPortal.follow(guest1Portal.siteId)
guest1EditorProxy2.updateSelections({
2: {range: {start: {row: 4, column: 4}, end: {row: 4, column: 4}}}
})
await condition(() => (
hostPortalDelegate.getTetherBufferProxyURI() === 'buffer-2' &&
deepEqual(hostPortalDelegate.getTetherPosition(), {row: 4, column: 4})
))
})
test('extending, retracting, and disconnecting when collaborating in a single editor', async () => {
const host = await buildClient()
const guest = await buildClient()
const hostPortal = await host.createPortal()
const hostBufferProxy = await hostPortal.createBufferProxy({uri: 'buffer-a', text: ('x'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy,
selections: {
1: {range: {start: {row: 5, column: 5}, end: {row: 6, column: 6}}},
2: {range: {start: {row: 8, column: 8}, end: {row: 9, column: 9}}}
}
})
hostPortal.activateEditorProxy(hostEditorProxy)
const guestPortalDelegate = new FakePortalDelegate()
const guestPortal = await guest.joinPortal(hostPortal.id)
await guestPortal.setDelegate(guestPortalDelegate)
// Guests immediately jump to host's cursor position after joining.
assert.equal(guestPortal.resolveFollowState(), FollowState.RETRACTED)
assert.deepEqual(guestPortalDelegate.getTetherPosition(), {row: 9, column: 9})
const guestEditorProxy = guestPortalDelegate.getTetherEditorProxy()
const guestEditorDelegate = new FakeEditorDelegate()
guestEditorDelegate.updateViewport(5, 15)
guestEditorProxy.setDelegate(guestEditorDelegate)
// Guests continue to follow host's cursor as it moves.
hostEditorProxy.updateSelections({
2: {range: {start: {row: 10, column: 10}, end: {row: 11, column: 11}}, reversed: true}
})
await condition(() => deepEqual(guestPortalDelegate.getTetherPosition(), {row: 10, column: 10}))
// Extend the tether when the guest explicitly moves their cursor
guestEditorProxy.updateSelections({
2: {range: {start: {row: 9, column: 9}, end: {row: 9, column: 9}}}
})
assert.equal(guestPortal.resolveFollowState(), FollowState.EXTENDED)
// When the tether is extended, the follower's cursor does not follow
// the tether's position as long as it remains visible in the viewport
assert(!guestEditorDelegate.isScrollNeededToViewPosition({row: 11, column: 11}))
hostEditorProxy.updateSelections({
2: {range: {start: {row: 11, column: 11}, end: {row: 11, column: 11}}}
})
await condition(() => deepEqual(
guestEditorDelegate.getSelectionsForSiteId(1)[2].range,
{start: {row: 11, column: 11}, end: {row: 11, column: 11}}
))
await condition(() => deepEqual(guestPortalDelegate.getTetherPosition(), {row: 10, column: 10}))
// Moves out of the viewport will retract the tether so long as the
// tether disconnect window has elapsed since the last cursor movement
// by the follower
await timeout(guestPortal.tetherDisconnectWindow)
assert(guestEditorDelegate.isScrollNeededToViewPosition({row: 20, column: 20}))
hostEditorProxy.updateSelections({
2: {range: {start: {row: 20, column: 20}, end: {row: 20, column: 20}}}
})
await condition(() => guestPortalDelegate.getTetherState() === FollowState.RETRACTED)
assert.deepEqual(guestPortalDelegate.getTetherPosition(), {row: 20, column: 20})
hostEditorProxy.updateSelections({
2: {range: {start: {row: 21, column: 21}, end: {row: 21, column: 21}}}
})
await condition(() => deepEqual(guestPortalDelegate.getTetherPosition(), {row: 21, column: 21}))
// Update delegate's tether position when text changes on remote sites.
hostBufferProxy.setTextInRange({row: 21, column: 0}, {row: 21, column: 0}, 'X')
await condition(() => deepEqual(guestPortalDelegate.getTetherPosition(), {row: 21, column: 22}))
// Extend the tether when the follower starts typing.
guestEditorProxy.bufferProxy.setTextInRange({row: 21, column: 22}, {row: 21, column: 22}, 'ABCD')
assert.equal(guestPortal.resolveFollowState(), FollowState.EXTENDED)
// Disconnects the tether if leader moves offscreen within the disconnect window.
hostEditorProxy.updateSelections({
2: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}
})
await condition(() => guestPortalDelegate.getTetherState() === FollowState.DISCONNECTED)
assert.deepEqual(
guestEditorDelegate.getSelectionsForSiteId(1)[2].range,
{start: {row: 0, column: 0}, end: {row: 0, column: 0}}
)
// Can reconnect tether after disconnecting
guestPortal.follow(1)
assert.equal(guestPortalDelegate.getTetherState(), FollowState.RETRACTED)
assert.deepEqual(guestPortalDelegate.getTetherPosition(), {row: 0, column: 0})
hostEditorProxy.updateSelections({
2: {range: {start: {row: 1, column: 1}, end: {row: 1, column: 1}}}
})
await condition(() => deepEqual(guestPortalDelegate.getTetherPosition(), {row: 1, column: 1}))
// Disconnects the tether if it moves off screen within the disconnect
// window of the follower moving their cursor
guestEditorProxy.updateSelections({
2: {range: {start: {row: 22, column: 22}, end: {row: 22, column: 22}}}
})
hostEditorProxy.updateSelections({
2: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}
})
await condition(() => guestPortalDelegate.getTetherState() === FollowState.DISCONNECTED)
assert.deepEqual(
guestEditorDelegate.getSelectionsForSiteId(1)[2].range,
{start: {row: 0, column: 0}, end: {row: 0, column: 0}}
)
// Can reconnect tether after disconnecting
guestPortal.follow(1)
assert.equal(guestPortalDelegate.getTetherState(), FollowState.RETRACTED)
assert.deepEqual(guestPortalDelegate.getTetherPosition(), {row: 0, column: 0})
hostEditorProxy.updateSelections({
2: {range: {start: {row: 1, column: 1}, end: {row: 1, column: 1}}}
})
await condition(() => deepEqual(guestPortalDelegate.getTetherPosition(), {row: 1, column: 1}))
// Disconnect tether when we scroll out of view. In real life, the
// viewport would have changed when we reconnected the tether, but in
// this test we're only concerned with the tether position being out of
// view when we indicate a scroll.
assert(guestEditorDelegate.isScrollNeededToViewPosition({row: 1, column: 1}))
guestEditorProxy.didScroll()
assert.equal(guestPortalDelegate.getTetherState(), FollowState.DISCONNECTED)
hostEditorProxy.updateSelections({
2: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}
})
await condition(() => deepEqual(
guestEditorDelegate.getSelectionsForSiteId(1)[2].range,
{start: {row: 0, column: 0}, end: {row: 0, column: 0}}
))
assert.notDeepEqual(guestPortalDelegate.getTetherPosition(), {row: 0, column: 0})
})
test('showing and hiding selections', async () => {
const host = await buildClient()
const hostPortal = await host.createPortal()
const hostBufferProxy1 = await hostPortal.createBufferProxy({uri: 'buffer-a', text: ('x'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy1 = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy1, selections: {
1: {range: {start: {row: 5, column: 5}, end: {row: 6, column: 6}}}
}})
const hostEditor1Delegate = new FakeEditorDelegate()
hostEditorProxy1.setDelegate(hostEditor1Delegate)
hostPortal.activateEditorProxy(hostEditorProxy1)
const guest = await buildClient()
const guestPortalDelegate = new FakePortalDelegate()
const guestPortal = await guest.joinPortal(hostPortal.id)
await guestPortal.setDelegate(guestPortalDelegate)
const guestEditor1Proxy = guestPortalDelegate.getTetherEditorProxy()
const guestEditor1Delegate = new FakeEditorDelegate()
guestEditor1Proxy.setDelegate(guestEditor1Delegate)
guestEditor1Proxy.updateSelections({
1: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}
}, {initialUpdate: true})
assert.deepEqual(guestPortalDelegate.getTetherPosition(), {row: 6, column: 6})
// Cursors are not rendered locally or remotely for followers with
// retracted tethers
await condition(() => deepEqual(hostEditor1Delegate.getSelectionsForSiteId(2), {}))
// When the tether is extended, selections appear
guestEditor1Proxy.updateSelections({
1: {range: {start: {row: 1, column: 1}, end: {row: 1, column: 1}}}
})
await condition(() => {
const selection = hostEditor1Delegate.getSelectionsForSiteId(2)[1]
return selection && deepEqual(selection.range, {start: {row: 1, column: 1}, end: {row: 1, column: 1}})
})
// Selections disappear when the tether is retracted again
await timeout(guestPortal.tetherDisconnectWindow)
guestEditor1Delegate.updateViewport(0, 6)
hostEditorProxy1.updateSelections({
1: {range: {start: {row: 12, column: 12}, end: {row: 12, column: 12}}}
})
await condition(() => deepEqual(guestPortal.resolveLeaderPosition(), {row: 12, column: 12}))
await condition(() => deepEqual(hostEditor1Delegate.getSelectionsForSiteId(2), {}))
// Disconnecting the tether shows the selections again
guestEditor1Delegate.updateViewport(6, 15)
guestEditor1Proxy.updateSelections({
1: {range: {start: {row: 13, column: 13}, end: {row: 13, column: 13}}}
})
hostEditorProxy1.updateSelections({
1: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}
})
await condition(() => {
const selection = hostEditor1Delegate.getSelectionsForSiteId(2)[1]
return selection && deepEqual(selection.range, {start: {row: 13, column: 13}, end: {row: 13, column: 13}})
})
// Activating an editor proxy hides selection on the previously active
// editor proxy and shows them on the new one.
const hostBufferProxy2 = await hostPortal.createBufferProxy({uri: 'buffer-b', text: ('x'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy2 = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy2, selections: {
1: {range: {start: {row: 1, column: 3}, end: {row: 2, column: 4}}}
}})
const hostEditor2Delegate = new FakeEditorDelegate()
hostEditorProxy2.setDelegate(hostEditor2Delegate)
hostPortal.activateEditorProxy(hostEditorProxy2)
await condition(() => guestPortal.getEditorProxiesMetadata().length === 2)
const guestEditor2ProxyMetadata = guestPortal.getEditorProxiesMetadata().find((e) => e.bufferProxyURI === 'buffer-b')
const guestEditor2Proxy = await guestPortal.findOrFetchEditorProxy(guestEditor2ProxyMetadata.id)
const guestEditor2Delegate = new FakeEditorDelegate()
guestEditor2Proxy.setDelegate(guestEditor2Delegate)
guestEditor2Proxy.updateSelections({
1: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}
}, {initialUpdate: true})
await condition(() => (
deepEqual(guestEditor1Delegate.getSelectionsForSiteId(1), {}) &&
!deepEqual(guestEditor2Delegate.getSelectionsForSiteId(1), {})
))
hostPortal.activateEditorProxy(hostEditorProxy1)
await condition(() => (
!deepEqual(guestEditor1Delegate.getSelectionsForSiteId(1), {}) &&
deepEqual(guestEditor2Delegate.getSelectionsForSiteId(1), {})
))
// Retracting the tether when the leader is on a different editor proxy hides follower selections.
assert.notDeepEqual(hostEditor1Delegate.getSelectionsForSiteId(2), {})
assert.deepEqual(hostEditor2Delegate.getSelectionsForSiteId(2), {})
guestPortal.follow(1)
await condition(() => (
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(2), {}) &&
deepEqual(hostEditor2Delegate.getSelectionsForSiteId(2), {})
))
})
test('transitive tethering (without cycles)', async () => {
const host = await buildClient()
const guest1 = await buildClient()
const guest2 = await buildClient()
const hostPortal = await host.createPortal()
const hostBufferProxy1 = await hostPortal.createBufferProxy({uri: 'buffer-a', text: ('x'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy1 = await hostPortal.createEditorProxy({
bufferProxy: hostBufferProxy1,
selections: {1: {range: {start: {row: 5, column: 5}, end: {row: 5, column: 5}}}}
})
const hostEditor1Delegate = new FakeEditorDelegate()
hostEditorProxy1.setDelegate(hostEditor1Delegate)
hostPortal.activateEditorProxy(hostEditorProxy1)
const guest1PortalDelegate = new FakePortalDelegate()
const guest1Portal = await guest1.joinPortal(hostPortal.id)
await guest1Portal.setDelegate(guest1PortalDelegate)
const guest1Editor1Proxy = guest1PortalDelegate.getTetherEditorProxy()
const guest1Editor1Delegate = new FakeEditorDelegate()
guest1Editor1Delegate.updateViewport(5, 15)
guest1Editor1Proxy.setDelegate(guest1Editor1Delegate)
const guest2PortalDelegate = new FakePortalDelegate()
const guest2Portal = await guest2.joinPortal(hostPortal.id)
await guest2Portal.setDelegate(guest2PortalDelegate)
const guest2Editor1Proxy = guest2PortalDelegate.getTetherEditorProxy()
const guest2Editor1Delegate = new FakeEditorDelegate()
guest2Editor1Delegate.updateViewport(5, 15)
guest2Editor1Proxy.setDelegate(guest2Editor1Delegate)
// Guest1 follows the host, and Guest2 follows Guest1. This has the effect
// of making Guest2 follow the host.
guest1Portal.follow(hostPortal.siteId)
guest2Portal.follow(guest1Portal.siteId)
hostEditorProxy1.updateSelections({
1: {range: {start: {row: 12, column: 12}, end: {row: 12, column: 12}}}
})
await condition(() => (
deepEqual(guest1PortalDelegate.getTetherPosition(), {row: 12, column: 12}) &&
deepEqual(guest2PortalDelegate.getTetherPosition(), {row: 12, column: 12}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(2), {}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(3), {})
))
// When tether is extended on Guest1, Guest2 momentarily stops following the host.
guest1Editor1Proxy.updateSelections({
1: {range: {start: {row: 1, column: 1}, end: {row: 1, column: 1}}}
})
await condition(() => {
const selection = hostEditor1Delegate.getSelectionsForSiteId(2)[1]
return (
selection && deepEqual(selection.range, {start: {row: 1, column: 1}, end: {row: 1, column: 1}}) &&
deepEqual(guest2PortalDelegate.getTetherPosition(), {row: 1, column: 1}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(3), {})
)
})
// When tether is retracted on Guest1, Guest2 goes back to following the host.
await timeout(guest1Portal.tetherDisconnectWindow)
guest1Editor1Delegate.updateViewport(0, 6)
hostEditorProxy1.updateSelections({
1: {range: {start: {row: 8, column: 0}, end: {row: 8, column: 0}}}
})
await condition(() => (
deepEqual(guest1PortalDelegate.getTetherPosition(), {row: 8, column: 0}) &&
deepEqual(guest2PortalDelegate.getTetherPosition(), {row: 8, column: 0}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(2), {}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(3), {})
))
// Ensure transitive following works for new sites that join after
// tethering has already been established.
const guest3 = await buildClient()
const guest3PortalDelegate = new FakePortalDelegate()
const guest3Portal = await guest3.joinPortal(hostPortal.id)
await guest3Portal.setDelegate(guest3PortalDelegate)
const guest3EditorProxy = guest3PortalDelegate.getTetherEditorProxy()
const guest3EditorDelegate = new FakeEditorDelegate()
guest3EditorDelegate.updateViewport(5, 15)
guest3EditorProxy.setDelegate(guest3EditorDelegate)
guest3Portal.follow(guest2Portal.siteId)
await condition(() => (
deepEqual(guest1PortalDelegate.getTetherPosition(), {row: 8, column: 0}) &&
deepEqual(guest2PortalDelegate.getTetherPosition(), {row: 8, column: 0}) &&
deepEqual(guest3PortalDelegate.getTetherPosition(), {row: 8, column: 0}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(guest1Portal.siteId), {}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(guest2Portal.siteId), {}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(guest3Portal.siteId), {})
))
// Ensure transitive following works when activating other editor proxies.
const hostBufferProxy2 = await hostPortal.createBufferProxy({uri: 'buffer-b', text: ('x'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy2 = await hostPortal.createEditorProxy({
bufferProxy: hostBufferProxy2,
selections: {1: {range: {start: {row: 5, column: 5}, end: {row: 5, column: 5}}}}
})
const hostEditor2Delegate = new FakeEditorDelegate()
hostEditorProxy2.setDelegate(hostEditor2Delegate)
hostPortal.activateEditorProxy(hostEditorProxy2)
await condition(() => (
deepEqual(guest1PortalDelegate.getTetherPosition(), {row: 5, column: 5}) &&
deepEqual(guest2PortalDelegate.getTetherPosition(), {row: 5, column: 5}) &&
deepEqual(guest3PortalDelegate.getTetherPosition(), {row: 5, column: 5}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(guest1Portal.siteId), {}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(guest2Portal.siteId), {}) &&
deepEqual(hostEditor1Delegate.getSelectionsForSiteId(guest3Portal.siteId), {})
))
// Disconnecting the tether on Guest1 breaks transitivity.
const guest1Editor2Proxy = guest1PortalDelegate.getTetherEditorProxy()
const guest1Editor2Delegate = new FakeEditorDelegate()
guest1Editor2Delegate.updateViewport(6, 15)
guest1Editor2Proxy.setDelegate(guest1Editor2Delegate)
guest1Editor2Proxy.updateSelections({
1: {range: {start: {row: 13, column: 13}, end: {row: 13, column: 13}}}
})
hostEditorProxy2.updateSelections({
1: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}
})
await condition(() => {
const selection = hostEditor2Delegate.getSelectionsForSiteId(guest1Portal.siteId)[1]
return (
selection && deepEqual(selection.range, {start: {row: 13, column: 13}, end: {row: 13, column: 13}}) &&
deepEqual(guest2PortalDelegate.getTetherPosition(), {row: 13, column: 13}) &&
deepEqual(hostEditor2Delegate.getSelectionsForSiteId(3), {})
)
})
})
test('transitive tethering (with cycles)', async () => {
const host = await buildClient()
const guest1 = await buildClient()
const guest2 = await buildClient()
const hostPortal = await host.createPortal()
const hostPortalDelegate = new FakePortalDelegate()
hostPortal.setDelegate(hostPortalDelegate)
const hostBufferProxy = await hostPortal.createBufferProxy({uri: 'buffer-a', text: ('x'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy = await hostPortal.createEditorProxy({
bufferProxy: hostBufferProxy,
selections: {1: {range: {start: {row: 5, column: 5}, end: {row: 5, column: 5}}}}
})
const hostEditorDelegate = new FakeEditorDelegate()
hostEditorProxy.setDelegate(hostEditorDelegate)
hostPortal.activateEditorProxy(hostEditorProxy)
const guest1PortalDelegate = new FakePortalDelegate()
const guest1Portal = await guest1.joinPortal(hostPortal.id)
await guest1Portal.setDelegate(guest1PortalDelegate)
const guest1EditorProxy = guest1PortalDelegate.getTetherEditorProxy()
const guest1EditorDelegate = new FakeEditorDelegate()
guest1EditorDelegate.updateViewport(5, 15)
guest1EditorProxy.setDelegate(guest1EditorDelegate)
const guest2PortalDelegate = new FakePortalDelegate()
const guest2Portal = await guest2.joinPortal(hostPortal.id)
await guest2Portal.setDelegate(guest2PortalDelegate)
const guest2EditorProxy = guest2PortalDelegate.getTetherEditorProxy()
const guest2EditorDelegate = new FakeEditorDelegate()
guest2EditorDelegate.updateViewport(5, 15)
guest2EditorProxy.setDelegate(guest2EditorDelegate)
// Disconnect guest1's tether.
guest1Portal.unfollow()
guest1EditorProxy.updateSelections({1: {range: range([0, 0], [0, 0])}})
await condition(() => {
const guest1SelectionOnHost = hostEditorDelegate.getSelectionsForSiteId(guest1Portal.siteId)[1]
const guest1SelectionOnGuest2 = guest2EditorDelegate.getSelectionsForSiteId(guest1Portal.siteId)[1]
return (
guest1SelectionOnHost && deepEqual(guest1SelectionOnHost.range, range([0, 0], [0, 0])) &&
guest1SelectionOnGuest2 && deepEqual(guest1SelectionOnGuest2.range, range([0, 0], [0, 0]))
)
})
// Form a cycle (guest1 -> guest2 -> host -> guest1) and ensure it gets
// broken on the site with the lowest site id.
guest1Portal.follow(guest2Portal.siteId)
guest2Portal.follow(hostPortal.siteId)
hostPortal.follow(guest1Portal.siteId)
await condition(() => (
deepEqual(hostPortalDelegate.getTetherPosition(), {row: 5, column: 5}) &&
deepEqual(guest1PortalDelegate.getTetherPosition(), {row: 5, column: 5}) &&
deepEqual(guest2PortalDelegate.getTetherPosition(), {row: 5, column: 5})
))
assert.equal(hostPortal.getFollowedSiteId(), null)
assert.equal(hostPortalDelegate.getTetherState(), FollowState.DISCONNECTED)
assert.equal(guest1Portal.getFollowedSiteId(), guest2Portal.siteId)
assert.equal(guest1PortalDelegate.getTetherState(), FollowState.RETRACTED)
assert.equal(guest2Portal.getFollowedSiteId(), hostPortal.siteId)
assert.equal(guest2PortalDelegate.getTetherState(), FollowState.RETRACTED)
// The site which breaks the cycle becomes the leader.
guest1EditorProxy.updateSelections({
1: {range: {start: {row: 13, column: 13}, end: {row: 13, column: 13}}}
})
guest1Portal.unfollow()
await condition(() => (
deepEqual(hostPortalDelegate.getTetherPosition(), {row: 13, column: 13}) &&
deepEqual(guest1PortalDelegate.getTetherPosition(), {row: 13, column: 13}) &&
deepEqual(guest2PortalDelegate.getTetherPosition(), {row: 13, column: 13})
))
assert.equal(hostPortal.getFollowedSiteId(), guest1Portal.siteId)
assert.equal(hostPortalDelegate.getTetherState(), FollowState.RETRACTED)
assert.equal(guest1Portal.getFollowedSiteId(), null)
assert.equal(guest1PortalDelegate.getTetherState(), FollowState.DISCONNECTED)
assert.equal(guest2Portal.getFollowedSiteId(), hostPortal.siteId)
assert.equal(guest2PortalDelegate.getTetherState(), FollowState.RETRACTED)
})
})
test('active positions of other collaborators', async () => {
const host = await buildClient()
const guest1 = await buildClient()
const guest2 = await buildClient()
const portals = []
const hostPortal = await host.createPortal()
const hostPortalDelegate = new FakePortalDelegate()
hostPortal.setDelegate(hostPortalDelegate)
portals.push(hostPortal)
const hostBufferProxy1 = await hostPortal.createBufferProxy({uri: 'buffer-a', text: ('x'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy1 = await hostPortal.createEditorProxy({
bufferProxy: hostBufferProxy1,
selections: {1: {range: {start: {row: 5, column: 5}, end: {row: 5, column: 5}}}}
})
hostPortal.activateEditorProxy(hostEditorProxy1)
const guest1PortalDelegate = new FakePortalDelegate()
const guest1Portal = await guest1.joinPortal(hostPortal.id)
await guest1Portal.setDelegate(guest1PortalDelegate)
portals.push(guest1Portal)
const guest2PortalDelegate = new FakePortalDelegate()
const guest2Portal = await guest2.joinPortal(hostPortal.id)
await guest2Portal.setDelegate(guest2PortalDelegate)
portals.push(guest2Portal)
// Update active positions after the leader updates its selections.
hostEditorProxy1.updateSelections({
1: {range: range([5, 4], [9, 6])},
2: {range: range([2, 7], [4, 4])}
})
await assertActivePositions([
{siteId: 1, editorProxyId: hostEditorProxy1.id, position: point(4, 4), followState: FollowState.DISCONNECTED},
{siteId: 2, editorProxyId: hostEditorProxy1.id, position: point(4, 4), followState: FollowState.RETRACTED},
{siteId: 3, editorProxyId: hostEditorProxy1.id, position: point(4, 4), followState: FollowState.RETRACTED}
])
// Update active positions after a text change.
hostBufferProxy1.setTextInRange(point(4, 0), point(4, 0), 'X')
await assertActivePositions([
{siteId: 1, editorProxyId: hostEditorProxy1.id, position: point(4, 5), followState: FollowState.DISCONNECTED},
{siteId: 2, editorProxyId: hostEditorProxy1.id, position: point(4, 5), followState: FollowState.RETRACTED},
{siteId: 3, editorProxyId: hostEditorProxy1.id, position: point(4, 5), followState: FollowState.RETRACTED}
])
// Update active positions after the leader changes.
guest1PortalDelegate.getTetherEditorProxy().updateSelections({
1: {range: range([5, 4], [9, 6]), reversed: true}
})
guest2Portal.follow(guest1Portal.siteId)
await assertActivePositions([
{siteId: 1, editorProxyId: hostEditorProxy1.id, position: point(4, 5), followState: FollowState.DISCONNECTED},
{siteId: 2, editorProxyId: hostEditorProxy1.id, position: point(5, 4), followState: FollowState.EXTENDED},
{siteId: 3, editorProxyId: hostEditorProxy1.id, position: point(5, 4), followState: FollowState.RETRACTED}
])
// Update active positions after host switches to a different editor proxy.
const hostBufferProxy2 = await hostPortal.createBufferProxy({uri: 'buffer-b', text: ('x'.repeat(30) + '\n').repeat(30)})
const hostEditorProxy2 = await hostPortal.createEditorProxy({
bufferProxy: hostBufferProxy2,
selections: {1: {range: {start: {row: 2, column: 2}, end: {row: 2, column: 2}}}}
})
hostPortal.activateEditorProxy(hostEditorProxy2)
await assertActivePositions([
{siteId: 1, editorProxyId: hostEditorProxy2.id, position: point(2, 2), followState: FollowState.DISCONNECTED},
{siteId: 2, editorProxyId: hostEditorProxy1.id, position: point(5, 4), followState: FollowState.DISCONNECTED},
{siteId: 3, editorProxyId: hostEditorProxy1.id, position: point(5, 4), followState: FollowState.RETRACTED}
])
// Update active positions after a guest switches to a different editor proxy.
const guest1EditorProxy2Metadata = guest1Portal.getEditorProxiesMetadata().find((e) => e.bufferProxyURI === 'buffer-b')
const guest1EditorProxy2 = await guest1Portal.findOrFetchEditorProxy(guest1EditorProxy2Metadata.id)
guest1Portal.activateEditorProxy(guest1EditorProxy2)
guest1EditorProxy2.updateSelections({
1: {range: range([1, 0], [1, 0])}
}, {initialUpdate: true})
await assertActivePositions([
{siteId: 1, editorProxyId: hostEditorProxy2.id, position: point(2, 2), followState: FollowState.DISCONNECTED},
{siteId: 2, editorProxyId: hostEditorProxy2.id, position: point(1, 0), followState: FollowState.DISCONNECTED},
{siteId: 3, editorProxyId: hostEditorProxy2.id, position: point(1, 0), followState: FollowState.RETRACTED}
])
// Update active positions after a site disconnects.
guest2Portal.dispose()
await assertActivePositions([
{siteId: 1, editorProxyId: hostEditorProxy2.id, position: point(2, 2), followState: FollowState.DISCONNECTED},
{siteId: 2, editorProxyId: hostEditorProxy2.id, position: point(1, 0), followState: FollowState.DISCONNECTED}
])
// Update active positions after participant switches focus to something outside of the portal.
hostPortal.activateEditorProxy(null)
await assertActivePositions([
{siteId: 1, editorProxyId: null, position: null, followState: null},
{siteId: 2, editorProxyId: hostEditorProxy2.id, position: point(1, 0), followState: FollowState.DISCONNECTED}
])
function assertActivePositions (expectedPositions) {
const alivePortals = portals.filter((p) => !p.disposed)
return condition(() =>
alivePortals.every((p) => deepEqual(p.delegate.getActivePositions(), expectedPositions))
)
}
})
test('disposing editor and buffer proxies', async () => {
const host = await buildClient()
const guest = await buildClient()
const hostPortal = await host.createPortal()
const hostBufferProxy = await hostPortal.createBufferProxy({uri: 'some-buffer', text: ''})
hostBufferProxy.setDelegate(new FakeBufferDelegate())
const hostEditorProxy1 = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy, selections: {}})
hostEditorProxy1.setDelegate(new FakeEditorDelegate())
const hostEditorProxy2 = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy, selections: {}})
hostEditorProxy2.setDelegate(new FakeEditorDelegate())
hostPortal.activateEditorProxy(hostEditorProxy1)
const guestPortal = await guest.joinPortal(hostPortal.id)
const guestPortalDelegate = new FakePortalDelegate()
await guestPortal.setDelegate(guestPortalDelegate)
await condition(() => guestPortalDelegate.getTetherEditorProxy() != null)
const guestEditorProxy1 = guestPortalDelegate.getTetherEditorProxy()
guestEditorProxy1.setDelegate(new FakeEditorDelegate())
hostPortal.activateEditorProxy(hostEditorProxy2)
await condition(() => guestPortalDelegate.getTetherEditorProxy() !== guestEditorProxy1)
const guestEditorProxy2 = guestPortalDelegate.getTetherEditorProxy()
guestEditorProxy2.setDelegate(new FakeEditorDelegate())
assert.equal(guestEditorProxy1.bufferProxy, guestEditorProxy2.bufferProxy)
const guestBufferProxy = guestEditorProxy1.bufferProxy
guestBufferProxy.setDelegate(new FakeBufferDelegate())
hostEditorProxy1.dispose()
assert(hostEditorProxy1.delegate.isDisposed())
await condition(() => guestEditorProxy1.delegate.isDisposed())
hostEditorProxy2.dispose()
assert(hostEditorProxy2.delegate.isDisposed())
await condition(() => guestEditorProxy2.delegate.isDisposed())
assert(!hostBufferProxy.delegate.isDisposed())
hostBufferProxy.dispose()
assert(hostBufferProxy.delegate.isDisposed())
await condition(() => guestBufferProxy.delegate.isDisposed())
})
test('changing buffer proxy uri', async () => {
const host = await buildClient()
const hostPortal = await host.createPortal()
const guest = await buildClient()
const guestPortal = await guest.joinPortal(hostPortal.id)
const hostBuffer1Proxy = await hostPortal.createBufferProxy({uri: 'buffer1-a', text: ''})
const hostEditor1Proxy = await hostPortal.createEditorProxy({bufferProxy: hostBuffer1Proxy, selections: {}})
const hostBuffer2Proxy = await hostPortal.createBufferProxy({uri: 'buffer2-a', text: ''})
const hostEditor2Proxy = await hostPortal.createEditorProxy({bufferProxy: hostBuffer2Proxy, selections: {}})
const guestEditor1Proxy = await guestPortal.findOrFetchEditorProxy(hostEditor1Proxy.id)
const guestBuffer1Proxy = guestEditor1Proxy.bufferProxy
guestBuffer1Proxy.setDelegate(new FakeBufferDelegate())
assert.equal(guestBuffer1Proxy.uri, 'buffer1-a')
assert.equal(guestPortal.getEditorProxyMetadata(hostEditor1Proxy.id).bufferProxyURI, 'buffer1-a')
assert.equal(guestPortal.getEditorProxyMetadata(hostEditor2Proxy.id).bufferProxyURI, 'buffer2-a')
hostBuffer1Proxy.setURI('buffer1-b')
hostBuffer2Proxy.setURI('buffer2-b')
await condition(() => guestBuffer1Proxy.delegate.uriChangeCount === 1)
assert.equal(guestBuffer1Proxy.uri, 'buffer1-b')
assert.equal(guestPortal.getEditorProxyMetadata(hostEditor1Proxy.id).bufferProxyURI, 'buffer1-b')
assert.equal(guestPortal.getEditorProxyMetadata(hostEditor2Proxy.id).bufferProxyURI, 'buffer2-b')
})
test('save requests', async () => {
const host = await buildClient()
const hostPortal = await host.createPortal()
const hostBufferProxy = await hostPortal.createBufferProxy({uri: 'buffer-a', text: ''})
const hostBufferDelegate = new FakeBufferDelegate('')
hostBufferProxy.setDelegate(hostBufferDelegate)
const hostEditorProxy = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy, selections: {}})
hostPortal.activateEditorProxy(hostEditorProxy)
const guest1 = await buildClient()
const guest1PortalDelegate = new FakePortalDelegate()
const guest1Portal = await guest1.joinPortal(hostPortal.id)
guest1Portal.setDelegate(guest1PortalDelegate)
const guest1EditorProxy = guest1PortalDelegate.getTetherEditorProxy()
const guest1BufferProxy = guest1EditorProxy.bufferProxy
const guest2 = await buildClient()
const guest2PortalDelegate = new FakePortalDelegate()
const guest2Portal = await guest2.joinPortal(hostPortal.id)
guest2Portal.setDelegate(guest2PortalDelegate)
const guest2EditorProxy = guest2PortalDelegate.getTetherEditorProxy()
const guest2BufferProxy = guest2EditorProxy.bufferProxy
guest1BufferProxy.requestSave()
guest2BufferProxy.requestSave()
await condition(() => hostBufferDelegate.saveRequestCount === 2)
})
suite('leaving, closing, or losing connection to a portal', () => {
let hostPortal, hostEditorDelegate
let guest1Portal, guest1PortalDelegate, guest1EditorDelegate
let guest2Portal, guest2PortalDelegate, guest2EditorDelegate
let guest3Portal, guest3PortalDelegate, guest3EditorDelegate
setup(async () => {
const host = await buildClient()
hostPortal = await host.createPortal()
const guest1 = await buildClient()
guest1PortalDelegate = new FakePortalDelegate()
guest1Portal = await guest1.joinPortal(hostPortal.id)
await guest1Portal.setDelegate(guest1PortalDelegate)
const guest2 = await buildClient()
guest2PortalDelegate = new FakePortalDelegate()
guest2Portal = await guest2.joinPortal(hostPortal.id)
await guest2Portal.setDelegate(guest2PortalDelegate)
const guest3 = await buildClient()
guest3PortalDelegate = new FakePortalDelegate()
guest3Portal = await guest3.joinPortal(hostPortal.id)
await guest3Portal.setDelegate(guest3PortalDelegate)
const hostBufferDelegate = new FakeBufferDelegate('')
const hostBufferProxy = await hostPortal.createBufferProxy({uri: 'some-buffer', text: hostBufferDelegate.text})
hostBufferProxy.setDelegate(hostBufferDelegate)
const hostEditorProxy = await hostPortal.createEditorProxy({bufferProxy: hostBufferProxy, selections: {}})
hostEditorDelegate = new FakeEditorDelegate()
hostEditorProxy.setDelegate(hostEditorDelegate)
hostPortal.activateEditorProxy(hostEditorProxy)
await condition(() =>
guest1PortalDelegate.getTetherEditorProxy() != null &&
guest2PortalDelegate.getTetherEditorProxy() != null &&
guest3PortalDelegate.getTetherEditorProxy() != null
)
const guest1EditorProxy = guest1PortalDelegate.getTetherEditorProxy()
const guest1BufferProxy = guest1EditorProxy.bufferProxy
const guest1BufferDelegate = new FakeBufferDelegate()
guest1BufferProxy.setDelegate(guest1BufferDelegate)
guest1EditorDelegate = new FakeEditorDelegate()
guest1EditorProxy.setDelegate(guest1EditorDelegate)
guest1EditorProxy.updateSelections(
{1: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}}
)
const guest2EditorProxy = guest2PortalDelegate.getTetherEditorProxy()
const guest2BufferProxy = guest2EditorProxy.bufferProxy
const guest2BufferDelegate = new FakeBufferDelegate()
guest2BufferProxy.setDelegate(guest2BufferDelegate)
guest2EditorDelegate = new FakeEditorDelegate()
guest2EditorProxy.setDelegate(guest2EditorDelegate)
guest2EditorProxy.updateSelections(
{1: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}}
)
const guest3EditorProxy = guest3PortalDelegate.getTetherEditorProxy()
const guest3BufferProxy = guest3EditorProxy.bufferProxy
const guest3BufferDelegate = new FakeBufferDelegate()
guest3BufferProxy.setDelegate(guest3BufferDelegate)
guest3EditorDelegate = new FakeEditorDelegate()
guest3EditorProxy.setDelegate(guest3EditorDelegate)
guest3EditorProxy.updateSelections(
{1: {range: {start: {row: 0, column: 0}, end: {row: 0, column: 0}}}}
)
await condition(() =>
hostEditorDelegate.getSelectionsForSiteId(guest1Portal.siteId) != null &&
hostEditorDelegate.getSelectionsForSiteId(guest2Portal.siteId) != null &&
hostEditorDelegate.getSelectionsForSiteId(guest3Portal.siteId) != null
)
})
test('guest leaving a portal', async () => {
guest1Portal.dispose()
await condition(() =>
hostEditorDelegate.getSelectionsForSiteId(guest1Portal.siteId) == null &&
guest2EditorDelegate.getSelectionsForSiteId(guest1Portal.siteId) == null &&
guest3EditorDelegate.getSelectionsForSiteId(guest1Portal.siteId) == null
)
assert(!hostEditorDelegate.isDisposed())
assert(!guest2EditorDelegate.isDisposed())
assert(!guest3EditorDelegate.isDisposed())
assert(hostEditorDelegate.getSelectionsForSiteId(guest2Portal.siteId))
assert(hostEditorDelegate.getSelectionsForSiteId(guest3Portal.siteId))
})
test('host closing a portal', async () => {
assert(!guest1PortalDelegate.hasHostClosedPortal() && !guest2PortalDelegate.hasHostClosedPortal() && !guest3PortalDelegate.h