@q9labsai/chalk-react-native 4.1.12 → 4.1.13
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/android.js +2 -2
- package/dist/{chunk-FVHH65FR.js → chunk-4NLA35HE.js} +1 -1
- package/dist/{chunk-LZ67SXGB.js → chunk-MK6OO4V7.js} +6 -0
- package/dist/client.js +1 -1
- package/dist/index.js +2 -2
- package/dist/ios-pad.js +2 -2
- package/dist/ios-phone.js +2 -2
- package/dist/ios.js +2 -2
- package/dist/macos.js +2 -2
- package/package.json +4 -4
- package/src/callkit/callkit.test.ts +12 -10
- package/src/space-client/create-native-space-client.ts +8 -0
- package/dist/test-support/test-renderer.d.ts +0 -20
- package/dist/test-support/test-renderer.d.ts.map +0 -1
- package/src/android/connection-service.test.ts +0 -114
- package/src/callkit/resolve-space-callkit-options.test.ts +0 -68
- package/src/client.test.ts +0 -12
- package/src/clipboard.test.ts +0 -79
- package/src/components/Chalk.test.tsx +0 -223
- package/src/components/EmbeddedWhiteboard.test.tsx +0 -92
- package/src/components/Entrance.test.tsx +0 -26
- package/src/components/EntranceView.android.test.tsx +0 -100
- package/src/components/EntranceView.ios-pad.test.tsx +0 -160
- package/src/components/EntranceView.ios-phone.test.tsx +0 -137
- package/src/components/EntranceView.test.tsx +0 -15
- package/src/components/ReactionPicker.test.tsx +0 -19
- package/src/components/RtcVideoView.test.tsx +0 -12
- package/src/components/SpaceView.android.test.tsx +0 -11
- package/src/components/SpaceView.ios-pad.test.tsx +0 -11
- package/src/components/SpaceView.ios-phone.test.tsx +0 -11
- package/src/components/SpaceView.macos.test.tsx +0 -11
- package/src/components/SpaceView.shared.test.tsx +0 -116
- package/src/components/SpaceView.test.tsx +0 -15
- package/src/components/android-connection-service-controller.test.ts +0 -80
- package/src/components/episode-diagnostic-render-observer.test.ts +0 -24
- package/src/components/logo/Logo.test.tsx +0 -144
- package/src/components/native-animation-controller.test.ts +0 -47
- package/src/components/native-chat.test.ts +0 -22
- package/src/components/native-entrance/entrance-controller-store.test.ts +0 -75
- package/src/components/native-media-visibility.test.ts +0 -77
- package/src/components/native-space-callkit-controller.test.ts +0 -62
- package/src/components/native-space-view/AppearanceSettings.test.tsx +0 -56
- package/src/components/native-space-view/ParticipantActionSheet.test.tsx +0 -18
- package/src/components/native-space-view/SettingsSheet.test.tsx +0 -17
- package/src/components/native-space-view/SpaceActionMenu.test.tsx +0 -126
- package/src/components/native-space-view/SpaceBottomDock.android.test.tsx +0 -61
- package/src/components/native-space-view/SpaceChatSheet.test.tsx +0 -18
- package/src/components/native-space-view/SpaceMoreSheet.test.tsx +0 -15
- package/src/components/native-space-view/SpacePanelSheet.test.tsx +0 -15
- package/src/components/native-space-view/SpacePeopleSheet.test.tsx +0 -13
- package/src/components/native-space-view/SpaceSurfacePrimitives.test.tsx +0 -12
- package/src/components/native-space-view/SpaceTopBar.android.test.tsx +0 -50
- package/src/components/native-space-view/SpaceWhiteboardSurface.test.tsx +0 -85
- package/src/components/native-space-view/diagnostics.test.ts +0 -31
- package/src/components/native-space-view/format-space-duration.test.ts +0 -13
- package/src/components/native-space-view/native-space-grid-keys.test.ts +0 -11
- package/src/components/native-space-view/native-space-page-store.test.ts +0 -23
- package/src/components/native-space-view/space-chat-attachments.test.ts +0 -33
- package/src/components/native-space-view/space-progressive-surface-helpers.test.ts +0 -24
- package/src/components/native-space-view/useSpaceViewController.contract.test.ts +0 -10
- package/src/components/native-space-view/useSpaceViewController.test.ts +0 -106
- package/src/components/native-space-view/useSpaceViewDerived.test.ts +0 -58
- package/src/components/native-space-view/useSpaceViewPanels.controlled.test.ts +0 -40
- package/src/components/native-space-view/useSpaceViewPanels.test.ts +0 -73
- package/src/components/space-lifecycle.test.ts +0 -19
- package/src/context/space-client-context.test.tsx +0 -10
- package/src/diagnostics.test.ts +0 -29
- package/src/hooks/entrance-preview-store.test.ts +0 -79
- package/src/hooks/space-hooks.test.ts +0 -76
- package/src/hooks/useLayout.test.ts +0 -16
- package/src/invites.test.ts +0 -51
- package/src/public-surface.test.ts +0 -26
- package/src/runtime.test.ts +0 -83
- package/src/space-client/cloudflare-rtk-native.test.ts +0 -118
- package/src/space-client/create-native-space-client.test.ts +0 -300
- package/src/telemetry.test.ts +0 -143
- package/src/test-support/test-renderer.test.ts +0 -9
- package/src/test-support/test-renderer.ts +0 -28
- package/src/ui/appearance.test.ts +0 -61
- package/src/ui/native-appearance-context.controlled.test.tsx +0 -41
- package/src/ui/native-appearance-context.test.tsx +0 -14
- package/src/ui/native-theme.test.ts +0 -34
- package/src/ui/native-theme.test.tsx +0 -14
- package/src/ui/participant-avatar.test.ts +0 -29
- package/src/ui/theme-tokens.test.ts +0 -14
- package/src/ui/theme.test.ts +0 -36
- package/src/utils/build-chalk-invite-link.test.ts +0 -12
- package/src/utils/ios-simulator.test.ts +0 -96
- package/src/utils/native-space-layout.test.ts +0 -170
- package/src/whiteboard/embedded-whiteboard-assets.test.ts +0 -31
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import type { SpaceClient, SpaceSnapshot } from "@q9labsai/chalk-client";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import type { ReactNode } from "react";
|
|
6
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
-
|
|
8
|
-
const testingLibrary = createRequire(import.meta.url)(join(process.cwd(), "../react/node_modules", "@testing-library/react")) as {
|
|
9
|
-
readonly act: (callback: () => void | Promise<void>) => void | Promise<void>;
|
|
10
|
-
readonly cleanup: () => void;
|
|
11
|
-
readonly render: (element: ReactNode) => { readonly container: HTMLElement; readonly unmount: () => void; readonly rerender: (element: ReactNode) => void };
|
|
12
|
-
};
|
|
13
|
-
const { act, cleanup, render } = testingLibrary;
|
|
14
|
-
|
|
15
|
-
const nativeClientFactory = vi.hoisted(() => vi.fn());
|
|
16
|
-
const spaceViewSpy = vi.hoisted(() => vi.fn(() => <div data-testid="space" />));
|
|
17
|
-
|
|
18
|
-
vi.mock("react-native", () => ({
|
|
19
|
-
StyleSheet: { create: <T,>(styles: T) => styles },
|
|
20
|
-
View: ({ children }: { readonly children?: ReactNode }) => children,
|
|
21
|
-
Text: ({ children }: { readonly children?: ReactNode }) => children,
|
|
22
|
-
Pressable: ({ children }: { readonly children?: ReactNode }) => children,
|
|
23
|
-
}));
|
|
24
|
-
vi.mock("../space-client/create-native-space-client", () => ({ createNativeSpaceClient: nativeClientFactory }));
|
|
25
|
-
vi.mock("./SpaceView", () => ({ SpaceView: spaceViewSpy }));
|
|
26
|
-
vi.mock("./Entrance", () => ({ Entrance: () => <div data-testid="entrance" /> }));
|
|
27
|
-
|
|
28
|
-
import { Chalk } from "./Chalk";
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
cleanup();
|
|
32
|
-
nativeClientFactory.mockReset();
|
|
33
|
-
spaceViewSpy.mockClear();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe("Chalk", () => {
|
|
37
|
-
it("fires joined and left only for the specified connection transitions", () => {
|
|
38
|
-
const client = createClient("joining");
|
|
39
|
-
const onJoined = vi.fn();
|
|
40
|
-
const onLeft = vi.fn();
|
|
41
|
-
render(<Chalk client={client.client} entrance={false} onJoined={onJoined} onLeft={onLeft} />);
|
|
42
|
-
|
|
43
|
-
act(() => client.setStatus("live"));
|
|
44
|
-
act(() => client.setStatus("reconnecting"));
|
|
45
|
-
act(() => client.setStatus("live"));
|
|
46
|
-
act(() => client.setStatus("left"));
|
|
47
|
-
|
|
48
|
-
expect(onJoined).toHaveBeenCalledTimes(1);
|
|
49
|
-
expect(onLeft).toHaveBeenCalledTimes(1);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("does not fire joined for an already-live supplied client", () => {
|
|
53
|
-
const client = createClient("live");
|
|
54
|
-
const onJoined = vi.fn();
|
|
55
|
-
render(<Chalk client={client.client} entrance={false} onJoined={onJoined} />);
|
|
56
|
-
|
|
57
|
-
expect(onJoined).not.toHaveBeenCalled();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("forwards the optional diagnostics action to the live Space", () => {
|
|
61
|
-
const client = createClient("live");
|
|
62
|
-
const onOpenDiagnostics = vi.fn();
|
|
63
|
-
render(<Chalk client={client.client} onOpenDiagnostics={onOpenDiagnostics} />);
|
|
64
|
-
|
|
65
|
-
expect(spaceViewSpy).toHaveBeenCalledWith(expect.objectContaining({ onOpenDiagnostics }), undefined);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("forwards canonical client events and cleans their subscriptions up", () => {
|
|
69
|
-
const client = createClient("live");
|
|
70
|
-
const handlers = new Map<string, (event: unknown) => void>();
|
|
71
|
-
const unsubscribe = vi.fn();
|
|
72
|
-
const eventClient = client.client as { on: (event: string, handler: unknown) => () => void };
|
|
73
|
-
eventClient.on = vi.fn((event, handler) => {
|
|
74
|
-
handlers.set(event, handler as (event: unknown) => void);
|
|
75
|
-
return unsubscribe;
|
|
76
|
-
});
|
|
77
|
-
const onError = vi.fn();
|
|
78
|
-
const view = render(<Chalk client={client.client} onEpisodeEnded={vi.fn()} onError={onError} onParticipantJoined={vi.fn()} onParticipantLeft={vi.fn()} onScreenShareStarted={vi.fn()} onScreenShareStopped={vi.fn()} />);
|
|
79
|
-
|
|
80
|
-
const error = { error: { code: "client.internal_error", message: "test", recoverable: true } };
|
|
81
|
-
handlers.get("error")?.(error);
|
|
82
|
-
expect(onError).toHaveBeenCalledWith(error);
|
|
83
|
-
|
|
84
|
-
view.unmount();
|
|
85
|
-
expect(unsubscribe).toHaveBeenCalledTimes(6);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("leaves an owned client before disposing it on unmount", async () => {
|
|
89
|
-
const client = createClient("joining");
|
|
90
|
-
let resolveLeave: (() => void) | undefined;
|
|
91
|
-
client.client.leave = vi.fn(
|
|
92
|
-
() =>
|
|
93
|
-
new Promise<void>((resolve) => {
|
|
94
|
-
resolveLeave = resolve;
|
|
95
|
-
}),
|
|
96
|
-
);
|
|
97
|
-
nativeClientFactory.mockReturnValue(client.client);
|
|
98
|
-
|
|
99
|
-
const view = render(<Chalk entrance={false} getAccess={vi.fn()} space="space-1" />);
|
|
100
|
-
view.unmount();
|
|
101
|
-
|
|
102
|
-
expect(client.client.leave).toHaveBeenCalledTimes(1);
|
|
103
|
-
expect(client.client.dispose).not.toHaveBeenCalled();
|
|
104
|
-
await act(async () => resolveLeave?.());
|
|
105
|
-
expect(client.client.dispose).toHaveBeenCalledTimes(1);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("keeps an owned SpaceClient when getAccess changes identity and uses the latest callback", async () => {
|
|
109
|
-
const client = createClient("idle");
|
|
110
|
-
const firstAccess = vi.fn(async () => ({ token: "first" }));
|
|
111
|
-
const latestAccess = vi.fn(async () => ({ token: "latest" }));
|
|
112
|
-
nativeClientFactory.mockReturnValue(client.client);
|
|
113
|
-
|
|
114
|
-
const view = render(<Chalk entrance getAccess={firstAccess} space="space-1" />);
|
|
115
|
-
view.rerender(<Chalk entrance getAccess={latestAccess} space="space-1" />);
|
|
116
|
-
|
|
117
|
-
expect(nativeClientFactory).toHaveBeenCalledTimes(1);
|
|
118
|
-
const access = nativeClientFactory.mock.calls[0]?.[0].getAccess as (input: unknown) => Promise<unknown>;
|
|
119
|
-
await expect(access({})).resolves.toEqual({ token: "latest" });
|
|
120
|
-
expect(firstAccess).not.toHaveBeenCalled();
|
|
121
|
-
expect(latestAccess).toHaveBeenCalledTimes(1);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("omits an absent display name when joining without Entrance", async () => {
|
|
125
|
-
const client = createClient("idle");
|
|
126
|
-
render(<Chalk client={client.client} entrance={false} />);
|
|
127
|
-
|
|
128
|
-
await act(async () => undefined);
|
|
129
|
-
expect(client.client.join).toHaveBeenCalledWith({ microphone: true, camera: true });
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("retries automatic join when an owned client is replaced for a new Space", async () => {
|
|
133
|
-
const first = createClient("idle");
|
|
134
|
-
const replacement = createClient("idle");
|
|
135
|
-
nativeClientFactory.mockReturnValueOnce(first.client).mockReturnValueOnce(replacement.client);
|
|
136
|
-
|
|
137
|
-
const view = render(<Chalk entrance={false} getAccess={vi.fn()} space="space-1" />);
|
|
138
|
-
await act(async () => undefined);
|
|
139
|
-
expect(first.client.join).toHaveBeenCalledWith({ microphone: true, camera: true });
|
|
140
|
-
|
|
141
|
-
view.rerender(<Chalk entrance={false} getAccess={vi.fn()} space="space-2" />);
|
|
142
|
-
await act(async () => undefined);
|
|
143
|
-
|
|
144
|
-
expect(nativeClientFactory).toHaveBeenCalledTimes(2);
|
|
145
|
-
expect(replacement.client.join).toHaveBeenCalledWith({ microphone: true, camera: true });
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("treats a replacement client's initial failure as an Entrance failure", () => {
|
|
149
|
-
const first = createClient("live");
|
|
150
|
-
const replacement = createClient("failed");
|
|
151
|
-
nativeClientFactory.mockReturnValueOnce(first.client).mockReturnValueOnce(replacement.client);
|
|
152
|
-
|
|
153
|
-
const view = render(<Chalk entrance getAccess={vi.fn()} space="space-1" />);
|
|
154
|
-
view.rerender(<Chalk entrance getAccess={vi.fn()} space="space-2" />);
|
|
155
|
-
|
|
156
|
-
expect(view.container.querySelector('[data-testid="entrance"]')).not.toBeNull();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("keeps the active Space visible during reconnecting and never after left", () => {
|
|
160
|
-
const client = createClient("live");
|
|
161
|
-
const view = render(<Chalk client={client.client} />);
|
|
162
|
-
|
|
163
|
-
expect(view.container.querySelector('[data-testid="space"]')).not.toBeNull();
|
|
164
|
-
act(() => client.setStatus("reconnecting"));
|
|
165
|
-
expect(view.container.querySelector('[data-testid="space"]')).not.toBeNull();
|
|
166
|
-
act(() => client.setStatus("left"));
|
|
167
|
-
expect(view.container.querySelector('[data-testid="space"]')).toBeNull();
|
|
168
|
-
expect(view.container.textContent).toContain("You have left this Space.");
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
function createClient(status: SpaceSnapshot["connection"]["status"]): { readonly client: SpaceClient; readonly setStatus: (nextStatus: SpaceSnapshot["connection"]["status"]) => void } {
|
|
173
|
-
let snapshot = createSnapshot(status);
|
|
174
|
-
const listeners = new Set<() => void>();
|
|
175
|
-
const client = {
|
|
176
|
-
getSnapshot: () => snapshot,
|
|
177
|
-
subscribe: (listener: () => void) => {
|
|
178
|
-
listeners.add(listener);
|
|
179
|
-
return () => listeners.delete(listener);
|
|
180
|
-
},
|
|
181
|
-
on: vi.fn(() => () => undefined),
|
|
182
|
-
join: vi.fn(async () => undefined),
|
|
183
|
-
leave: vi.fn(async () => undefined),
|
|
184
|
-
dispose: vi.fn(),
|
|
185
|
-
endEpisode: vi.fn(async () => undefined),
|
|
186
|
-
media: {},
|
|
187
|
-
chat: {},
|
|
188
|
-
participants: {},
|
|
189
|
-
reactions: {},
|
|
190
|
-
whiteboard: {},
|
|
191
|
-
} as unknown as SpaceClient;
|
|
192
|
-
return {
|
|
193
|
-
client,
|
|
194
|
-
setStatus(nextStatus) {
|
|
195
|
-
snapshot = { ...snapshot, connection: { ...snapshot.connection, status: nextStatus } };
|
|
196
|
-
listeners.forEach((listener) => listener());
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function createSnapshot(status: SpaceSnapshot["connection"]["status"]): SpaceSnapshot {
|
|
202
|
-
const capabilities = [] as const;
|
|
203
|
-
return {
|
|
204
|
-
connection: { status, episode: null, lastError: null },
|
|
205
|
-
self: { participantId: null, displayName: null, role: null, capabilities, handRaised: false, can: () => false },
|
|
206
|
-
participants: { roster: [], admissionQueue: [] },
|
|
207
|
-
media: {
|
|
208
|
-
devices: { microphones: [], cameras: [], speakers: [] },
|
|
209
|
-
selection: { microphone: null, camera: null, speaker: null },
|
|
210
|
-
local: {
|
|
211
|
-
microphone: { source: "microphone", state: "disabled", track: null },
|
|
212
|
-
camera: { source: "camera", state: "disabled", track: null },
|
|
213
|
-
screen: { source: "screen", state: "disabled", track: null },
|
|
214
|
-
},
|
|
215
|
-
remote: [],
|
|
216
|
-
screenShare: { source: "screen", state: "disabled", track: null },
|
|
217
|
-
incomingRequests: [],
|
|
218
|
-
},
|
|
219
|
-
chat: { status: "idle", messages: [], pendingSends: [], readReceipts: [], unreadCount: 0, pagination: { cursor: null, hasOlder: false, historyTruncated: false }, lastError: null },
|
|
220
|
-
reactions: { active: [] },
|
|
221
|
-
whiteboard: { open: false, engine: { status: "unsubscribed", sceneId: null, revision: null, presenting: false, error: null } },
|
|
222
|
-
};
|
|
223
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
const nativeTheme = vi.hoisted(() => ({
|
|
4
|
-
colorScheme: "light" as const,
|
|
5
|
-
colors: {
|
|
6
|
-
dangerSurfaceOverlay94: "#danger",
|
|
7
|
-
info: "#info",
|
|
8
|
-
lightMutedText: "#muted",
|
|
9
|
-
lightSubtleText: "#subtle",
|
|
10
|
-
lightSurface: "#surface",
|
|
11
|
-
lightText: "#text",
|
|
12
|
-
onDark: "#on-dark",
|
|
13
|
-
},
|
|
14
|
-
}));
|
|
15
|
-
const controllerOptions = vi.hoisted(() => [] as unknown[]);
|
|
16
|
-
|
|
17
|
-
vi.mock("react", () => ({
|
|
18
|
-
useCallback: <T,>(callback: T) => callback,
|
|
19
|
-
useEffect: (effect: () => void) => effect(),
|
|
20
|
-
useMemo: <T,>(factory: () => T) => factory(),
|
|
21
|
-
useRef: <T,>(current: T) => ({ current }),
|
|
22
|
-
useState: <T,>(initial: T | (() => T)): readonly [T, () => void] => [typeof initial === "function" ? (initial as () => T)() : initial, () => undefined],
|
|
23
|
-
}));
|
|
24
|
-
vi.mock("@q9labsai/chalk-whiteboard/embedded", () => ({
|
|
25
|
-
ChalkWhiteboardController: class {
|
|
26
|
-
constructor(options: unknown) {
|
|
27
|
-
controllerOptions.push(options);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
rendererReloaded(): void {}
|
|
31
|
-
|
|
32
|
-
setCapabilities(): void {}
|
|
33
|
-
|
|
34
|
-
setViewport(): void {}
|
|
35
|
-
|
|
36
|
-
start(): void {}
|
|
37
|
-
|
|
38
|
-
stop(): void {}
|
|
39
|
-
},
|
|
40
|
-
}));
|
|
41
|
-
vi.mock("react-native", () => ({
|
|
42
|
-
ActivityIndicator: "ActivityIndicator",
|
|
43
|
-
Pressable: "Pressable",
|
|
44
|
-
StyleSheet: { create: <Styles,>(styles: Styles) => styles },
|
|
45
|
-
Text: "Text",
|
|
46
|
-
View: "View",
|
|
47
|
-
useWindowDimensions: () => ({ width: 320, height: 640, scale: 2 }),
|
|
48
|
-
}));
|
|
49
|
-
vi.mock("react-native-webview", () => ({ default: "WebView" }));
|
|
50
|
-
vi.mock("../ui/native-theme", () => ({ useNativeTheme: () => nativeTheme }));
|
|
51
|
-
vi.mock("../whiteboard/embedded-whiteboard-assets", () => ({
|
|
52
|
-
isEmbeddedWhiteboardNavigationAllowed: () => true,
|
|
53
|
-
rendererURLWithContext: (url: string) => url,
|
|
54
|
-
resolveEmbeddedWhiteboardRendererURL: async () => "file:///android_asset/chalk-whiteboard/index.html",
|
|
55
|
-
}));
|
|
56
|
-
|
|
57
|
-
import type { ChalkEmbeddedWhiteboardTransport } from "@q9labsai/chalk-whiteboard/embedded";
|
|
58
|
-
import { EmbeddedWhiteboard, embeddedWhiteboardWebViewSecurityPolicy } from "./EmbeddedWhiteboard";
|
|
59
|
-
|
|
60
|
-
describe("EmbeddedWhiteboard WebView policy", () => {
|
|
61
|
-
it("keeps the offline renderer isolated from files, networks, storage, and cookies", () => {
|
|
62
|
-
expect(embeddedWhiteboardWebViewSecurityPolicy).toEqual({
|
|
63
|
-
allowFileAccess: true,
|
|
64
|
-
allowFileAccessFromFileURLs: false,
|
|
65
|
-
allowUniversalAccessFromFileURLs: false,
|
|
66
|
-
cacheEnabled: false,
|
|
67
|
-
domStorageEnabled: false,
|
|
68
|
-
javaScriptCanOpenWindowsAutomatically: false,
|
|
69
|
-
javaScriptEnabled: true,
|
|
70
|
-
mixedContentMode: "never",
|
|
71
|
-
setSupportMultipleWindows: false,
|
|
72
|
-
sharedCookiesEnabled: false,
|
|
73
|
-
thirdPartyCookiesEnabled: false,
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("uses the active NativeTheme colors throughout its loading surface", () => {
|
|
78
|
-
controllerOptions.length = 0;
|
|
79
|
-
const rendered = EmbeddedWhiteboard({
|
|
80
|
-
canClear: false,
|
|
81
|
-
canDraw: true,
|
|
82
|
-
journeyId: "journey-test",
|
|
83
|
-
transport: {} as ChalkEmbeddedWhiteboardTransport,
|
|
84
|
-
});
|
|
85
|
-
const children = rendered.props.children as readonly [{ readonly props: { readonly color: string } }, { readonly props: { readonly style: readonly unknown[] } }];
|
|
86
|
-
|
|
87
|
-
expect(rendered.props.style).toContainEqual({ backgroundColor: "#surface" });
|
|
88
|
-
expect(children[0]?.props.color).toBe("#info");
|
|
89
|
-
expect(children[1]?.props.style).toContainEqual({ color: "#muted" });
|
|
90
|
-
expect(controllerOptions[0]).toMatchObject({ theme: "light" });
|
|
91
|
-
});
|
|
92
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
vi.mock("./EntranceView", () => ({ EntranceView: "EntranceView" }));
|
|
4
|
-
vi.mock("./logo/Logo", () => ({ Logo: "Logo" }));
|
|
5
|
-
vi.mock("react-native", () => ({ Image: "Image" }));
|
|
6
|
-
|
|
7
|
-
import { Entrance } from "./Entrance";
|
|
8
|
-
|
|
9
|
-
describe("Entrance", () => {
|
|
10
|
-
it("exposes the preparation component", () => {
|
|
11
|
-
expect(Entrance).toBeTypeOf("function");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("forwards the public cancel callback to the native Entrance view", () => {
|
|
15
|
-
const onCancel = vi.fn();
|
|
16
|
-
const element = Entrance({ onCancel, onJoin: vi.fn(), spaceName: "Space" });
|
|
17
|
-
|
|
18
|
-
expect(element.props.onCancel).toBe(onCancel);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("uses the SDK wordmark when no custom logo URL is supplied", () => {
|
|
22
|
-
const element = Entrance({ onJoin: vi.fn(), spaceName: "Space" });
|
|
23
|
-
|
|
24
|
-
expect(element.props.logo).toMatchObject({ type: "Logo", props: { height: 32, variant: "wordmark" } });
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { isValidElement, type ReactElement, type ReactNode } from "react";
|
|
2
|
-
import { describe, expect, it, vi } from "vitest";
|
|
3
|
-
|
|
4
|
-
const controller = vi.hoisted(() => ({
|
|
5
|
-
displayName: "Ada",
|
|
6
|
-
audioEnabled: true,
|
|
7
|
-
videoEnabled: true,
|
|
8
|
-
isSubmitting: false,
|
|
9
|
-
isInputFocused: false,
|
|
10
|
-
previewError: null,
|
|
11
|
-
previewStream: null,
|
|
12
|
-
simulatorMediaDisabled: false,
|
|
13
|
-
setDisplayName: vi.fn(),
|
|
14
|
-
setInputFocused: vi.fn(),
|
|
15
|
-
toggleAudio: vi.fn(),
|
|
16
|
-
toggleVideo: vi.fn(),
|
|
17
|
-
handleJoin: vi.fn(),
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
const theme = vi.hoisted(() => ({
|
|
21
|
-
colors: {
|
|
22
|
-
background: "#101112",
|
|
23
|
-
foreground: "#f7f8f9",
|
|
24
|
-
primary: "#12ab89",
|
|
25
|
-
primaryForeground: "#ffffff",
|
|
26
|
-
muted: "#343536",
|
|
27
|
-
mutedForeground: "#909192",
|
|
28
|
-
error: "#dc2626",
|
|
29
|
-
card: "#202122",
|
|
30
|
-
surface: "#303132",
|
|
31
|
-
raisedSurface: "#404142",
|
|
32
|
-
insetSurface: "#505152",
|
|
33
|
-
controlsBackground: "#606162",
|
|
34
|
-
border: "#707172",
|
|
35
|
-
ring: "#808182",
|
|
36
|
-
darkCanvas: "#010203",
|
|
37
|
-
},
|
|
38
|
-
}));
|
|
39
|
-
|
|
40
|
-
vi.mock("react-native", () => ({
|
|
41
|
-
KeyboardAvoidingView: "KeyboardAvoidingView",
|
|
42
|
-
Platform: { OS: "android" },
|
|
43
|
-
Pressable: "Pressable",
|
|
44
|
-
StyleSheet: { absoluteFillObject: {}, create: <T,>(styles: T) => styles },
|
|
45
|
-
Text: "Text",
|
|
46
|
-
TextInput: "TextInput",
|
|
47
|
-
View: "View",
|
|
48
|
-
}));
|
|
49
|
-
vi.mock("@hugeicons/react-native", () => ({ HugeiconsIcon: "HugeiconsIcon" }));
|
|
50
|
-
vi.mock("../media/native-webrtc", () => ({ RTCView: "RTCView" }));
|
|
51
|
-
vi.mock("../ui/native-theme", () => ({ useNativeTheme: () => theme }));
|
|
52
|
-
vi.mock("./FaceAvatar", () => ({ FaceAvatar: "FaceAvatar" }));
|
|
53
|
-
vi.mock("./native-entrance/useEntranceController", () => ({ useEntranceController: () => controller }));
|
|
54
|
-
|
|
55
|
-
import { EntranceViewAndroid } from "./EntranceView.android";
|
|
56
|
-
|
|
57
|
-
describe("EntranceViewAndroid", () => {
|
|
58
|
-
it("applies the active theme to its native surface and media controls", () => {
|
|
59
|
-
const entrance = EntranceViewAndroid({ onJoin: vi.fn(), spaceName: "Design review" });
|
|
60
|
-
const screen = findElements(entrance, (element) => element.type === "KeyboardAvoidingView")[0];
|
|
61
|
-
const mediaControl = findElements(entrance, (element) => element.type === "Pressable" && !element.props.accessibilityLabel)[0];
|
|
62
|
-
const mediaStyle = mediaControl?.props.style as ((state: { readonly pressed: boolean }) => unknown) | undefined;
|
|
63
|
-
|
|
64
|
-
expect(JSON.stringify(screen?.props.style)).toContain(theme.colors.background);
|
|
65
|
-
expect(JSON.stringify(mediaStyle?.({ pressed: false }))).toContain(theme.colors.controlsBackground);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("omits the Android cancel control when no cancellation handler is supplied", () => {
|
|
69
|
-
const withoutCancel = EntranceViewAndroid({ onJoin: vi.fn(), spaceName: "Design review" });
|
|
70
|
-
const withCancel = EntranceViewAndroid({ onCancel: vi.fn(), onJoin: vi.fn(), spaceName: "Design review" });
|
|
71
|
-
|
|
72
|
-
expect(findElements(withoutCancel, (element) => element.props.accessibilityLabel === "Cancel and leave Entrance")).toHaveLength(0);
|
|
73
|
-
expect(findElements(withCancel, (element) => element.props.accessibilityLabel === "Cancel and leave Entrance")).toHaveLength(1);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
interface NativeElementProps {
|
|
78
|
-
readonly accessibilityLabel?: string;
|
|
79
|
-
readonly children?: ReactNode;
|
|
80
|
-
readonly style?: unknown;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function findElements(node: ReactNode, matches: (element: ReactElement<NativeElementProps>) => boolean): ReactElement<NativeElementProps>[] {
|
|
84
|
-
const found: ReactElement<NativeElementProps>[] = [];
|
|
85
|
-
if (!isValidElement<NativeElementProps>(node)) {
|
|
86
|
-
return found;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (matches(node)) {
|
|
90
|
-
found.push(node);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const children = node.props.children;
|
|
94
|
-
for (const child of Array.isArray(children) ? children : [children]) {
|
|
95
|
-
if (child !== undefined && child !== null) {
|
|
96
|
-
found.push(...findElements(child, matches));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return found;
|
|
100
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import type { ReactElement, ReactNode } from "react";
|
|
3
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
-
|
|
5
|
-
import { renderHook } from "../test-support/test-renderer";
|
|
6
|
-
|
|
7
|
-
const controller = vi.hoisted(() => ({
|
|
8
|
-
displayName: "Ada",
|
|
9
|
-
audioEnabled: true,
|
|
10
|
-
videoEnabled: true,
|
|
11
|
-
isSubmitting: false,
|
|
12
|
-
isInputFocused: false,
|
|
13
|
-
previewError: null,
|
|
14
|
-
previewStream: null,
|
|
15
|
-
simulatorMediaDisabled: false,
|
|
16
|
-
setDisplayName: vi.fn(),
|
|
17
|
-
setInputFocused: vi.fn(),
|
|
18
|
-
toggleAudio: vi.fn(),
|
|
19
|
-
toggleVideo: vi.fn(),
|
|
20
|
-
handleJoin: vi.fn(),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
const theme = vi.hoisted(() => ({
|
|
24
|
-
colors: {
|
|
25
|
-
background: "#101112",
|
|
26
|
-
foreground: "#f7f8f9",
|
|
27
|
-
primary: "#12ab89",
|
|
28
|
-
primaryForeground: "#ffffff",
|
|
29
|
-
mutedForeground: "#909192",
|
|
30
|
-
error: "#dc2626",
|
|
31
|
-
card: "#202122",
|
|
32
|
-
surface: "#303132",
|
|
33
|
-
border: "#707172",
|
|
34
|
-
ring: "#808182",
|
|
35
|
-
darkCanvas: "#010203",
|
|
36
|
-
controlsBackground: "#606162",
|
|
37
|
-
},
|
|
38
|
-
}));
|
|
39
|
-
|
|
40
|
-
vi.mock("react-native", () => {
|
|
41
|
-
class AnimatedValue {
|
|
42
|
-
interpolate(input: unknown): { readonly input: unknown } {
|
|
43
|
-
return { input };
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
ActivityIndicator: "ActivityIndicator",
|
|
49
|
-
Animated: {
|
|
50
|
-
Value: AnimatedValue,
|
|
51
|
-
View: "Animated.View",
|
|
52
|
-
delay: vi.fn(),
|
|
53
|
-
loop: vi.fn(),
|
|
54
|
-
sequence: vi.fn(),
|
|
55
|
-
spring: vi.fn(),
|
|
56
|
-
stagger: vi.fn(),
|
|
57
|
-
timing: vi.fn(),
|
|
58
|
-
},
|
|
59
|
-
KeyboardAvoidingView: "KeyboardAvoidingView",
|
|
60
|
-
Platform: { OS: "ios" },
|
|
61
|
-
Pressable: "Pressable",
|
|
62
|
-
StyleSheet: { absoluteFillObject: {}, create: <T,>(styles: T) => styles },
|
|
63
|
-
Text: "Text",
|
|
64
|
-
TextInput: "TextInput",
|
|
65
|
-
View: "View",
|
|
66
|
-
};
|
|
67
|
-
});
|
|
68
|
-
vi.mock("@hugeicons/react-native", () => ({ HugeiconsIcon: "HugeiconsIcon" }));
|
|
69
|
-
vi.mock("./FaceAvatar", () => ({ FaceAvatar: "FaceAvatar" }));
|
|
70
|
-
vi.mock("./RtcVideoView", () => ({ RtcVideoView: "RtcVideoView", hasRtcVideoView: () => false }));
|
|
71
|
-
vi.mock("../ui/native-theme", () => ({ useNativeTheme: () => theme }));
|
|
72
|
-
vi.mock("../utils/ios-simulator", () => ({ getIosSimulatorMediaMessage: () => "Media is unavailable in the simulator." }));
|
|
73
|
-
vi.mock("./native-animation-controller", () => ({ createAnimationRefController: () => () => undefined }));
|
|
74
|
-
vi.mock("./native-entrance/useEntranceController", () => ({ useEntranceController: () => controller }));
|
|
75
|
-
|
|
76
|
-
import { EntranceViewIosPad } from "./EntranceView.ios-pad";
|
|
77
|
-
|
|
78
|
-
beforeEach(() => {
|
|
79
|
-
controller.displayName = "Ada";
|
|
80
|
-
controller.audioEnabled = true;
|
|
81
|
-
controller.videoEnabled = true;
|
|
82
|
-
controller.isSubmitting = false;
|
|
83
|
-
controller.isInputFocused = false;
|
|
84
|
-
controller.previewError = null;
|
|
85
|
-
controller.previewStream = null;
|
|
86
|
-
controller.simulatorMediaDisabled = false;
|
|
87
|
-
controller.setDisplayName.mockReset();
|
|
88
|
-
controller.setInputFocused.mockReset();
|
|
89
|
-
controller.toggleAudio.mockReset();
|
|
90
|
-
controller.toggleVideo.mockReset();
|
|
91
|
-
controller.handleJoin.mockReset();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe("EntranceViewIosPad", () => {
|
|
95
|
-
it("composes the immersive surface with themed title, media controls, and cancel action", () => {
|
|
96
|
-
const onCancel = vi.fn();
|
|
97
|
-
const { result } = renderHook(() => EntranceViewIosPad({ onCancel, onJoin: vi.fn(), spaceName: "Design review" }));
|
|
98
|
-
const tree = result.current;
|
|
99
|
-
|
|
100
|
-
const screen = findElement(tree, "KeyboardAvoidingView");
|
|
101
|
-
const cancel = findElement(tree, "Pressable", (element) => element.props.accessibilityLabel === "Cancel and leave Entrance");
|
|
102
|
-
const microphone = findElement(tree, "Pressable", (element) => element.props.accessibilityLabel === "Mute microphone");
|
|
103
|
-
const camera = findElement(tree, "Pressable", (element) => element.props.accessibilityLabel === "Turn camera off");
|
|
104
|
-
const join = findElement(tree, "Pressable", (element) => element.props.accessibilityLabel === "Enter Space");
|
|
105
|
-
|
|
106
|
-
expect(screen?.props.style).toEqual(expect.arrayContaining([expect.objectContaining({ backgroundColor: theme.colors.background })]));
|
|
107
|
-
expect(findText(tree)).toContain("Design review");
|
|
108
|
-
expect(cancel).toBeDefined();
|
|
109
|
-
expect(microphone).toBeDefined();
|
|
110
|
-
expect(camera).toBeDefined();
|
|
111
|
-
expect(join?.props.accessibilityState).toEqual({ disabled: false });
|
|
112
|
-
|
|
113
|
-
cancel?.props.onPress();
|
|
114
|
-
expect(onCancel).toHaveBeenCalledOnce();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("uses the avatar fallback and disables joining until a name is entered", () => {
|
|
118
|
-
controller.displayName = "";
|
|
119
|
-
|
|
120
|
-
const { result } = renderHook(() => EntranceViewIosPad({ onJoin: vi.fn(), spaceName: "Quiet Space" }));
|
|
121
|
-
const tree = result.current;
|
|
122
|
-
const join = findElement(tree, "Pressable", (element) => element.props.accessibilityLabel === "Enter Space");
|
|
123
|
-
|
|
124
|
-
expect(findElement(tree, "FaceAvatar")).toBeDefined();
|
|
125
|
-
expect(findElement(tree, "RtcVideoView")).toBeUndefined();
|
|
126
|
-
expect(join?.props.accessibilityState).toEqual({ disabled: true });
|
|
127
|
-
expect(findText(tree)).toContain("Quiet Space");
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
interface NativeElementProps {
|
|
132
|
-
readonly accessibilityLabel?: string;
|
|
133
|
-
readonly accessibilityState?: unknown;
|
|
134
|
-
readonly children?: ReactNode;
|
|
135
|
-
readonly onPress?: () => void;
|
|
136
|
-
readonly style?: unknown;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function findElement(node: ReactNode, type: string, matches: (element: ReactElement<NativeElementProps>) => boolean = () => true): ReactElement<NativeElementProps> | undefined {
|
|
140
|
-
if (!isElement(node)) return undefined;
|
|
141
|
-
if (node.type === type && matches(node)) return node;
|
|
142
|
-
|
|
143
|
-
const children = node.props.children;
|
|
144
|
-
for (const child of Array.isArray(children) ? children : [children]) {
|
|
145
|
-
const found = findElement(child, type, matches);
|
|
146
|
-
if (found) return found;
|
|
147
|
-
}
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function findText(node: ReactNode): string[] {
|
|
152
|
-
if (typeof node === "string") return [node];
|
|
153
|
-
if (!isElement(node)) return [];
|
|
154
|
-
const children = node.props.children;
|
|
155
|
-
return (Array.isArray(children) ? children : [children]).flatMap(findText);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function isElement(node: ReactNode): node is ReactElement<NativeElementProps> {
|
|
159
|
-
return typeof node === "object" && node !== null && "type" in node && "props" in node;
|
|
160
|
-
}
|