@dxos/react-ui 0.1.39 → 0.1.41-next.adfd7c6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +35 -19
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +21 -2
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/types/src/components/IdentityList/IdentityListItem.d.ts.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationListContainer.d.ts.map +1 -1
- package/dist/types/src/panels/DevicesPanel/DevicesPanel.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/joinMachine.d.ts.map +1 -1
- package/dist/types/src/playwright/invitations-manager.d.ts +4 -3
- package/dist/types/src/playwright/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/stories/Invitations.stories.d.ts.map +1 -1
- package/dist/types/src/testing/shell-manager.d.ts +2 -1
- package/dist/types/src/testing/shell-manager.d.ts.map +1 -1
- package/package.json +13 -13
- package/src/components/IdentityList/IdentityListItem.tsx +4 -1
- package/src/components/InvitationList/InvitationListContainer.tsx +1 -2
- package/src/panels/DevicesPanel/DevicesPanel.tsx +1 -3
- package/src/panels/JoinPanel/joinMachine.ts +34 -18
- package/src/panels/SpacePanel/SpacePanel.tsx +1 -1
- package/src/playwright/invitations-manager.ts +24 -18
- package/src/playwright/invitations.spec.ts +58 -45
- package/src/stories/Invitations.stories.tsx +25 -3
- package/src/testing/shell-manager.ts +20 -2
|
@@ -31,7 +31,10 @@ export const IdentityListItem = ({
|
|
|
31
31
|
...(presence === SpaceMember.PresenceState.OFFLINE && {
|
|
32
32
|
status: 'inactive',
|
|
33
33
|
description: (
|
|
34
|
-
<p
|
|
34
|
+
<p
|
|
35
|
+
className='font-system-normal text-xs text-neutral-700 dark:text-neutral-300'
|
|
36
|
+
data-testid='identity-list-item.description'
|
|
37
|
+
>
|
|
35
38
|
{t('identity offline description')}
|
|
36
39
|
</p>
|
|
37
40
|
)
|
|
@@ -19,8 +19,7 @@ export const InvitationListContainer = ({ spaceKey, createInvitationUrl }: Invit
|
|
|
19
19
|
const invitations = useSpaceInvitations(spaceKey);
|
|
20
20
|
const onClickRemove = useCallback(
|
|
21
21
|
(invitation: CancellableInvitationObservable) => {
|
|
22
|
-
|
|
23
|
-
invitationId && space?.removeInvitation(invitationId);
|
|
22
|
+
void invitation.cancel();
|
|
24
23
|
},
|
|
25
24
|
[space]
|
|
26
25
|
);
|
|
@@ -64,9 +64,7 @@ const DeviceListView = ({ createInvitationUrl, titleId, onDone, doneActionParent
|
|
|
64
64
|
<div role='region' className={mx(defaultSurface, 'rounded-be-md p-2')}>
|
|
65
65
|
<InvitationList
|
|
66
66
|
invitations={invitations}
|
|
67
|
-
onClickRemove={(invitation) =>
|
|
68
|
-
invitation.get() && client.halo.removeInvitation(invitation.get().invitationId)
|
|
69
|
-
}
|
|
67
|
+
onClickRemove={(invitation) => invitation.cancel()}
|
|
70
68
|
createInvitationUrl={createInvitationUrl}
|
|
71
69
|
/>
|
|
72
70
|
<Button
|
|
@@ -322,24 +322,38 @@ const joinMachine = createMachine<JoinMachineContext, JoinEvent>(
|
|
|
322
322
|
identity: () => null
|
|
323
323
|
}),
|
|
324
324
|
resetInvitation: assign<JoinMachineContext, EmptyInvitationEvent>({
|
|
325
|
-
halo: (context, event) =>
|
|
326
|
-
event.type
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
325
|
+
halo: (context, event) => {
|
|
326
|
+
if (event.type !== 'resetHaloInvitation') {
|
|
327
|
+
return context.halo;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (context.halo.invitationObservable) {
|
|
331
|
+
void context.halo.invitationObservable.cancel();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
...context.halo,
|
|
336
|
+
invitation: undefined,
|
|
337
|
+
invitationObservable: undefined,
|
|
338
|
+
invitationSubscribable: undefined
|
|
339
|
+
};
|
|
340
|
+
},
|
|
341
|
+
space: (context, event) => {
|
|
342
|
+
if (event.type !== 'resetSpaceInvitation') {
|
|
343
|
+
return context.space;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (context.space.invitationObservable) {
|
|
347
|
+
void context.space.invitationObservable.cancel();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
...context.space,
|
|
352
|
+
invitation: undefined,
|
|
353
|
+
invitationObservable: undefined,
|
|
354
|
+
invitationSubscribable: undefined
|
|
355
|
+
};
|
|
356
|
+
}
|
|
343
357
|
}),
|
|
344
358
|
setInvitation: assign<JoinMachineContext, SetInvitationEvent>({
|
|
345
359
|
halo: (context, event) =>
|
|
@@ -397,6 +411,7 @@ const useJoinMachine = (client: Client, options?: Parameters<typeof useMachine<J
|
|
|
397
411
|
},
|
|
398
412
|
[client]
|
|
399
413
|
);
|
|
414
|
+
|
|
400
415
|
const redeemSpaceInvitationCode = useCallback(
|
|
401
416
|
({ space }: JoinMachineContext) => {
|
|
402
417
|
if (space.unredeemedCode) {
|
|
@@ -414,6 +429,7 @@ const useJoinMachine = (client: Client, options?: Parameters<typeof useMachine<J
|
|
|
414
429
|
},
|
|
415
430
|
[client]
|
|
416
431
|
);
|
|
432
|
+
|
|
417
433
|
return useMachine(joinMachine, {
|
|
418
434
|
...options,
|
|
419
435
|
actions: {
|
|
@@ -52,7 +52,7 @@ const CurrentSpaceView = observer(({ space, createInvitationUrl, titleId }: Spac
|
|
|
52
52
|
<div role='region' className={mx(defaultSurface, 'rounded-be-md p-2')}>
|
|
53
53
|
<InvitationList
|
|
54
54
|
invitations={invitations}
|
|
55
|
-
onClickRemove={(invitation) => invitation.
|
|
55
|
+
onClickRemove={(invitation) => invitation.cancel()}
|
|
56
56
|
createInvitationUrl={createInvitationUrl}
|
|
57
57
|
/>
|
|
58
58
|
<Button
|
|
@@ -34,7 +34,7 @@ export class InvitationsManager extends ShellManager {
|
|
|
34
34
|
|
|
35
35
|
const { page } = await setupPage(this._browser, {
|
|
36
36
|
url: storybookUrl('invitations--default'),
|
|
37
|
-
waitFor: (page) => page.getByTestId('invitations.identity-header').
|
|
37
|
+
waitFor: (page) => page.getByTestId('invitations.identity-header').first().isVisible()
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
this.page = page;
|
|
@@ -44,9 +44,24 @@ export class InvitationsManager extends ShellManager {
|
|
|
44
44
|
|
|
45
45
|
// Getters
|
|
46
46
|
|
|
47
|
+
peer(id: number) {
|
|
48
|
+
return this.page.getByTestId(`peer-${id}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getNetworkStatus(id: number) {
|
|
52
|
+
const selector = this.peer(id).getByTestId('identity-list-item.description').first();
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
await selector.waitFor({ timeout: 500 });
|
|
56
|
+
return ConnectionState.OFFLINE;
|
|
57
|
+
} catch {
|
|
58
|
+
return ConnectionState.ONLINE;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
async getDisplayName(id: number) {
|
|
48
63
|
// TODO(wittjosiah): Update id.
|
|
49
|
-
return this.peer(id).getByTestId('identity-list-item').
|
|
64
|
+
return this.peer(id).getByTestId('identity-list-item').first().textContent();
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
async getSpaceName(id: number, nth: number) {
|
|
@@ -54,15 +69,15 @@ export class InvitationsManager extends ShellManager {
|
|
|
54
69
|
return this.peer(id).getByTestId('space-list-item').nth(nth).textContent();
|
|
55
70
|
}
|
|
56
71
|
|
|
72
|
+
async getAuthCode(): Promise<string> {
|
|
73
|
+
this._authCode = new Trigger<string>();
|
|
74
|
+
return await this._authCode.wait();
|
|
75
|
+
}
|
|
76
|
+
|
|
57
77
|
// Actions
|
|
58
78
|
|
|
59
|
-
async
|
|
60
|
-
await this.
|
|
61
|
-
({ id, state }) => {
|
|
62
|
-
(window as any)[`peer${id}client`].mesh.setConnectionState(state);
|
|
63
|
-
},
|
|
64
|
-
{ id, state }
|
|
65
|
-
);
|
|
79
|
+
async toggleNetworkStatus(id: number) {
|
|
80
|
+
await this.peer(id).getByTestId('invitations.toggle-network').click();
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
async openPanel(id: number, panel: PanelType) {
|
|
@@ -137,15 +152,6 @@ export class InvitationsManager extends ShellManager {
|
|
|
137
152
|
await this.page.keyboard.press('Enter');
|
|
138
153
|
}
|
|
139
154
|
|
|
140
|
-
async getAuthCode(): Promise<string> {
|
|
141
|
-
this._authCode = new Trigger<string>();
|
|
142
|
-
return await this._authCode.wait();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
peer(id: number) {
|
|
146
|
-
return this.page.getByTestId(`peer-${id}`);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
155
|
private async _onConsoleMessage(message: ConsoleMessage) {
|
|
150
156
|
try {
|
|
151
157
|
const json = JSON.parse(message.text());
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import { test } from '@playwright/test';
|
|
6
6
|
import { expect } from 'chai';
|
|
7
|
-
import waitForExpect from 'wait-for-expect';
|
|
8
7
|
|
|
9
8
|
import { sleep } from '@dxos/async';
|
|
10
9
|
import { ConnectionState, Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
@@ -14,6 +13,13 @@ import { InvitationsManager } from './invitations-manager';
|
|
|
14
13
|
test.describe('Invitations', () => {
|
|
15
14
|
let manager: InvitationsManager;
|
|
16
15
|
|
|
16
|
+
// TODO(wittjosiah): Storybook takes a bit to be ready for testing against.
|
|
17
|
+
test.beforeAll(async ({ browser }) => {
|
|
18
|
+
test.setTimeout(60_000);
|
|
19
|
+
manager = new InvitationsManager(browser);
|
|
20
|
+
await manager.init();
|
|
21
|
+
});
|
|
22
|
+
|
|
17
23
|
test.beforeEach(async ({ browser }) => {
|
|
18
24
|
manager = new InvitationsManager(browser);
|
|
19
25
|
await manager.init();
|
|
@@ -46,6 +52,8 @@ test.describe('Invitations', () => {
|
|
|
46
52
|
});
|
|
47
53
|
|
|
48
54
|
test('invalid & retry auth code', async () => {
|
|
55
|
+
test.slow();
|
|
56
|
+
|
|
49
57
|
await manager.createIdentity(0);
|
|
50
58
|
await manager.openPanel(0, 'devices');
|
|
51
59
|
const invitation = await manager.createInvitation(0, 'device');
|
|
@@ -61,6 +69,8 @@ test.describe('Invitations', () => {
|
|
|
61
69
|
});
|
|
62
70
|
|
|
63
71
|
test('invalid & max auth code retries reached, retry invitation', async () => {
|
|
72
|
+
test.slow();
|
|
73
|
+
|
|
64
74
|
await manager.createIdentity(0);
|
|
65
75
|
await manager.openPanel(0, 'devices');
|
|
66
76
|
const invitation = await manager.createInvitation(0, 'device');
|
|
@@ -95,10 +105,7 @@ test.describe('Invitations', () => {
|
|
|
95
105
|
await manager.openPanel(1, 'identity');
|
|
96
106
|
await manager.acceptInvitation(1, 'device', invitation);
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
await waitForExpect(async () => {
|
|
100
|
-
expect(await manager.invitationFailed(manager.peer(1))).to.be.true;
|
|
101
|
-
}, 100);
|
|
108
|
+
expect(await manager.invitationFailed(manager.peer(1))).to.be.true;
|
|
102
109
|
});
|
|
103
110
|
|
|
104
111
|
test('invitation cancelled by host', async () => {
|
|
@@ -108,14 +115,10 @@ test.describe('Invitations', () => {
|
|
|
108
115
|
|
|
109
116
|
await manager.openPanel(1, 'identity');
|
|
110
117
|
await manager.acceptInvitation(1, 'device', invitation);
|
|
111
|
-
|
|
112
|
-
await sleep(100);
|
|
118
|
+
expect(await manager.readyToAuthenticate('device', manager.peer(1))).to.be.true;
|
|
113
119
|
await manager.cancelInvitation('device', 'host', manager.peer(0));
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
await waitForExpect(async () => {
|
|
117
|
-
expect(await manager.invitationFailed(manager.peer(1))).to.be.true;
|
|
118
|
-
}, 100);
|
|
121
|
+
expect(await manager.invitationFailed(manager.peer(1))).to.be.true;
|
|
119
122
|
});
|
|
120
123
|
|
|
121
124
|
test('invitation cancelled by guest & retry', async () => {
|
|
@@ -136,18 +139,19 @@ test.describe('Invitations', () => {
|
|
|
136
139
|
});
|
|
137
140
|
|
|
138
141
|
test('recover from network failure during invitation', async () => {
|
|
142
|
+
test.slow();
|
|
143
|
+
|
|
139
144
|
await manager.createIdentity(0);
|
|
140
145
|
await manager.openPanel(0, 'devices');
|
|
141
146
|
const invitation = await manager.createInvitation(0, 'device');
|
|
142
147
|
|
|
143
148
|
await manager.openPanel(1, 'identity');
|
|
144
149
|
await manager.acceptInvitation(1, 'device', invitation);
|
|
145
|
-
await
|
|
146
|
-
await manager.
|
|
147
|
-
await
|
|
148
|
-
await manager.
|
|
149
|
-
|
|
150
|
-
await manager.authenticateInvitation('device', '00000', manager.peer(1));
|
|
150
|
+
expect(await manager.readyToAuthenticate('device', manager.peer(1))).to.be.true;
|
|
151
|
+
await manager.toggleNetworkStatus(0);
|
|
152
|
+
expect(await manager.getNetworkStatus(0)).to.equal(ConnectionState.OFFLINE);
|
|
153
|
+
await manager.toggleNetworkStatus(0);
|
|
154
|
+
expect(await manager.getNetworkStatus(0)).to.equal(ConnectionState.ONLINE);
|
|
151
155
|
await manager.resetInvitation(manager.peer(1));
|
|
152
156
|
await manager.invitationInputContinue('device', manager.peer(1));
|
|
153
157
|
const [authCode] = await Promise.all([manager.getAuthCode(), manager.clearAuthCode('device', manager.peer(1))]);
|
|
@@ -158,24 +162,30 @@ test.describe('Invitations', () => {
|
|
|
158
162
|
});
|
|
159
163
|
|
|
160
164
|
test('multiple concurrent invitations', async () => {
|
|
165
|
+
test.slow();
|
|
166
|
+
|
|
161
167
|
await manager.createIdentity(0);
|
|
162
168
|
await manager.openPanel(0, 'devices');
|
|
163
|
-
const invitation1 = await manager.createInvitation(0, 'device');
|
|
164
|
-
const invitation2 = await manager.createInvitation(0, 'device');
|
|
165
|
-
|
|
166
169
|
await manager.openPanel(1, 'identity');
|
|
167
170
|
await manager.openPanel(2, 'identity');
|
|
171
|
+
|
|
172
|
+
// TODO(wittjosiah): Improve auth code fetching to make it easier to disambiguate them.
|
|
173
|
+
const invitation1 = await manager.createInvitation(0, 'device');
|
|
168
174
|
const [authCode1] = await Promise.all([
|
|
169
175
|
manager.getAuthCode(),
|
|
170
176
|
manager.acceptInvitation(1, 'device', invitation1)
|
|
171
177
|
]);
|
|
172
|
-
|
|
173
|
-
await
|
|
178
|
+
|
|
179
|
+
const invitation2 = await manager.createInvitation(0, 'device');
|
|
174
180
|
const [authCode2] = await Promise.all([
|
|
175
181
|
manager.getAuthCode(),
|
|
176
182
|
manager.acceptInvitation(2, 'device', invitation2)
|
|
177
183
|
]);
|
|
184
|
+
|
|
178
185
|
await manager.authenticateInvitation('device', authCode1, manager.peer(1));
|
|
186
|
+
// TODO(wittjosiah): Managing focus in tests is flaky.
|
|
187
|
+
// Helps to ensure both auth codes are fully input (especially in webkit).
|
|
188
|
+
await sleep(100);
|
|
179
189
|
await manager.authenticateInvitation('device', authCode2, manager.peer(2));
|
|
180
190
|
await manager.doneInvitation('device', manager.peer(1));
|
|
181
191
|
await manager.doneInvitation('device', manager.peer(2));
|
|
@@ -219,6 +229,8 @@ test.describe('Invitations', () => {
|
|
|
219
229
|
});
|
|
220
230
|
|
|
221
231
|
test('invalid & retry auth code', async () => {
|
|
232
|
+
test.slow();
|
|
233
|
+
|
|
222
234
|
await manager.createIdentity(0);
|
|
223
235
|
await manager.createSpace(0);
|
|
224
236
|
await manager.openPanel(0, 0);
|
|
@@ -240,6 +252,8 @@ test.describe('Invitations', () => {
|
|
|
240
252
|
});
|
|
241
253
|
|
|
242
254
|
test('invalid & max auth code retries reached, retry invitation', async () => {
|
|
255
|
+
test.slow();
|
|
256
|
+
|
|
243
257
|
await manager.createIdentity(0);
|
|
244
258
|
await manager.createSpace(0);
|
|
245
259
|
await manager.openPanel(0, 0);
|
|
@@ -279,10 +293,7 @@ test.describe('Invitations', () => {
|
|
|
279
293
|
await manager.openPanel(1, 'join');
|
|
280
294
|
await manager.acceptInvitation(1, 'space', invitation);
|
|
281
295
|
|
|
282
|
-
|
|
283
|
-
await waitForExpect(async () => {
|
|
284
|
-
expect(await manager.invitationFailed(manager.peer(1))).to.be.true;
|
|
285
|
-
}, 100);
|
|
296
|
+
expect(await manager.invitationFailed(manager.peer(1))).to.be.true;
|
|
286
297
|
});
|
|
287
298
|
|
|
288
299
|
test('invitation cancelled by host', async () => {
|
|
@@ -294,14 +305,10 @@ test.describe('Invitations', () => {
|
|
|
294
305
|
await manager.createIdentity(1);
|
|
295
306
|
await manager.openPanel(1, 'join');
|
|
296
307
|
await manager.acceptInvitation(1, 'space', invitation);
|
|
297
|
-
|
|
298
|
-
await sleep(100);
|
|
308
|
+
expect(await manager.readyToAuthenticate('space', manager.peer(1))).to.be.true;
|
|
299
309
|
await manager.cancelInvitation('space', 'host', manager.peer(0));
|
|
300
310
|
|
|
301
|
-
|
|
302
|
-
await waitForExpect(async () => {
|
|
303
|
-
expect(await manager.invitationFailed(manager.peer(1))).to.be.true;
|
|
304
|
-
}, 100);
|
|
311
|
+
expect(await manager.invitationFailed(manager.peer(1))).to.be.true;
|
|
305
312
|
});
|
|
306
313
|
|
|
307
314
|
test('invitation cancelled by guest & retry', async () => {
|
|
@@ -327,6 +334,8 @@ test.describe('Invitations', () => {
|
|
|
327
334
|
});
|
|
328
335
|
|
|
329
336
|
test('recover from network failure during invitation', async () => {
|
|
337
|
+
test.slow();
|
|
338
|
+
|
|
330
339
|
await manager.createIdentity(0);
|
|
331
340
|
await manager.createSpace(0);
|
|
332
341
|
await manager.openPanel(0, 0);
|
|
@@ -335,12 +344,11 @@ test.describe('Invitations', () => {
|
|
|
335
344
|
await manager.createIdentity(1);
|
|
336
345
|
await manager.openPanel(1, 'join');
|
|
337
346
|
await manager.acceptInvitation(1, 'space', invitation);
|
|
338
|
-
await
|
|
339
|
-
await manager.
|
|
340
|
-
await
|
|
341
|
-
await manager.
|
|
342
|
-
|
|
343
|
-
await manager.authenticateInvitation('space', '00000', manager.peer(1));
|
|
347
|
+
expect(await manager.readyToAuthenticate('space', manager.peer(1))).to.be.true;
|
|
348
|
+
await manager.toggleNetworkStatus(0);
|
|
349
|
+
expect(await manager.getNetworkStatus(0)).to.equal(ConnectionState.OFFLINE);
|
|
350
|
+
await manager.toggleNetworkStatus(0);
|
|
351
|
+
expect(await manager.getNetworkStatus(0)).to.equal(ConnectionState.ONLINE);
|
|
344
352
|
await manager.resetInvitation(manager.peer(1));
|
|
345
353
|
await manager.invitationInputContinue('space', manager.peer(1));
|
|
346
354
|
const [authCode] = await Promise.all([manager.getAuthCode(), manager.clearAuthCode('space', manager.peer(1))]);
|
|
@@ -352,21 +360,26 @@ test.describe('Invitations', () => {
|
|
|
352
360
|
});
|
|
353
361
|
|
|
354
362
|
test('multiple concurrent invitations', async () => {
|
|
355
|
-
|
|
356
|
-
await manager.createSpace(0);
|
|
357
|
-
await manager.openPanel(0, 0);
|
|
358
|
-
const invitation1 = await manager.createInvitation(0, 'space');
|
|
359
|
-
const invitation2 = await manager.createInvitation(0, 'space');
|
|
363
|
+
test.slow();
|
|
360
364
|
|
|
365
|
+
await manager.createIdentity(0);
|
|
361
366
|
await manager.createIdentity(1);
|
|
362
367
|
await manager.createIdentity(2);
|
|
368
|
+
|
|
369
|
+
await manager.createSpace(0);
|
|
370
|
+
await manager.openPanel(0, 0);
|
|
363
371
|
await manager.openPanel(1, 'join');
|
|
364
372
|
await manager.openPanel(2, 'join');
|
|
373
|
+
|
|
374
|
+
const invitation1 = await manager.createInvitation(0, 'space');
|
|
365
375
|
const [authCode1] = await Promise.all([manager.getAuthCode(), manager.acceptInvitation(1, 'space', invitation1)]);
|
|
366
|
-
|
|
367
|
-
await
|
|
376
|
+
|
|
377
|
+
const invitation2 = await manager.createInvitation(0, 'space');
|
|
368
378
|
const [authCode2] = await Promise.all([manager.getAuthCode(), manager.acceptInvitation(2, 'space', invitation2)]);
|
|
379
|
+
|
|
369
380
|
await manager.authenticateInvitation('space', authCode1, manager.peer(1));
|
|
381
|
+
// Helps to ensure both auth codes are fully input (especially in webkit).
|
|
382
|
+
await sleep(100);
|
|
370
383
|
await manager.authenticateInvitation('space', authCode2, manager.peer(2));
|
|
371
384
|
await manager.doneInvitation('space', manager.peer(1));
|
|
372
385
|
await manager.doneInvitation('space', manager.peer(2));
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
6
|
import { faker } from '@faker-js/faker';
|
|
7
|
-
import { Intersect, Laptop, Planet, Plus, PlusCircle, QrCode } from '@phosphor-icons/react';
|
|
7
|
+
import { Intersect, Laptop, Planet, Plus, PlusCircle, QrCode, WifiHigh, WifiSlash } from '@phosphor-icons/react';
|
|
8
8
|
import React, { useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { ConnectionState, SpaceMember } from '@dxos/protocols/proto/dxos/client/services';
|
|
11
|
+
import { Space, SpaceProxy, useClient, useIdentity, useNetworkStatus, useSpaces } from '@dxos/react-client';
|
|
11
12
|
import { ClientDecorator } from '@dxos/react-client/testing';
|
|
12
13
|
import { Button, ButtonGroup, getSize, Group } from '@dxos/react-components';
|
|
13
14
|
|
|
@@ -96,6 +97,7 @@ const Panel = ({ id, panel, setPanel }: { id: number; panel?: PanelType; setPane
|
|
|
96
97
|
|
|
97
98
|
const render = ({ id }: { id: number }) => {
|
|
98
99
|
const client = useClient();
|
|
100
|
+
const networkStatus = useNetworkStatus().state;
|
|
99
101
|
const identity = useIdentity();
|
|
100
102
|
const [panel, setPanel] = useState<PanelType>();
|
|
101
103
|
|
|
@@ -138,6 +140,22 @@ const render = ({ id }: { id: number }) => {
|
|
|
138
140
|
<Planet weight='fill' className={getSize(6)} />
|
|
139
141
|
</Button>
|
|
140
142
|
{/* </Tooltip> */}
|
|
143
|
+
{/* <ToolTip content='Toggle Network'> */}
|
|
144
|
+
<Button
|
|
145
|
+
onClick={() =>
|
|
146
|
+
client.mesh.setConnectionState(
|
|
147
|
+
networkStatus === ConnectionState.ONLINE ? ConnectionState.OFFLINE : ConnectionState.ONLINE
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
data-testid='invitations.toggle-network'
|
|
151
|
+
>
|
|
152
|
+
{networkStatus === ConnectionState.ONLINE ? (
|
|
153
|
+
<WifiHigh weight='fill' className={getSize(6)} />
|
|
154
|
+
) : (
|
|
155
|
+
<WifiSlash weight='fill' className={getSize(6)} />
|
|
156
|
+
)}
|
|
157
|
+
</Button>
|
|
158
|
+
{/* </ToolTip> */}
|
|
141
159
|
</ButtonGroup>
|
|
142
160
|
);
|
|
143
161
|
|
|
@@ -152,7 +170,11 @@ const render = ({ id }: { id: number }) => {
|
|
|
152
170
|
return (
|
|
153
171
|
<div className='flex flex-col p-4 flex-1 min-w-0' data-testid={`peer-${id}`}>
|
|
154
172
|
<Group label={{ children: header }} className='mbe-2'>
|
|
155
|
-
{identity ?
|
|
173
|
+
{identity ? (
|
|
174
|
+
<IdentityListItem identity={identity} presence={networkStatus as unknown as SpaceMember.PresenceState} />
|
|
175
|
+
) : (
|
|
176
|
+
<div className='text-center'>No identity</div>
|
|
177
|
+
)}
|
|
156
178
|
</Group>
|
|
157
179
|
{identity || panel ? <Panel id={id} panel={panel} setPanel={setPanel} /> : null}
|
|
158
180
|
</div>
|
|
@@ -13,8 +13,14 @@ export class ShellManager {
|
|
|
13
13
|
return (scope || this.page).getByTestId(`${type === 'device' ? 'halo' : 'space'}-auth-code-input`).isVisible();
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
invitationFailed(scope?: Scope) {
|
|
17
|
-
|
|
16
|
+
async invitationFailed(scope?: Scope, timeout = 3000) {
|
|
17
|
+
const peer = scope || this.page;
|
|
18
|
+
try {
|
|
19
|
+
await peer.locator('[data-testid=invitation-rescuer-reset]:not([disabled])').waitFor({ timeout });
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
async inputInvitation(type: 'device' | 'space', invitation: string, scope?: Scope) {
|
|
@@ -36,6 +42,18 @@ export class ShellManager {
|
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
async readyToAuthenticate(type: 'device' | 'space', scope?: Scope, timeout = 3000) {
|
|
46
|
+
const peer = scope || this.page;
|
|
47
|
+
try {
|
|
48
|
+
await peer
|
|
49
|
+
.locator(`[data-testid=${type === 'device' ? 'halo' : 'space'}-auth-code-input]:not([disabled])`)
|
|
50
|
+
.waitFor({ timeout });
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
async authenticateInvitation(type: 'device' | 'space', authCode: string, scope?: Scope) {
|
|
40
58
|
const peer = scope || this.page;
|
|
41
59
|
// TODO(wittjosiah): Update ids.
|