@q9labsai/chalk-react-native 4.1.12 → 4.1.14

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.
Files changed (90) hide show
  1. package/dist/android.js +2 -2
  2. package/dist/{chunk-FVHH65FR.js → chunk-4NLA35HE.js} +1 -1
  3. package/dist/{chunk-LZ67SXGB.js → chunk-MK6OO4V7.js} +6 -0
  4. package/dist/client.js +1 -1
  5. package/dist/index.js +2 -2
  6. package/dist/ios-pad.js +2 -2
  7. package/dist/ios-phone.js +2 -2
  8. package/dist/ios.js +2 -2
  9. package/dist/macos.js +2 -2
  10. package/package.json +4 -4
  11. package/src/callkit/callkit.test.ts +12 -10
  12. package/src/space-client/create-native-space-client.ts +8 -0
  13. package/dist/test-support/test-renderer.d.ts +0 -20
  14. package/dist/test-support/test-renderer.d.ts.map +0 -1
  15. package/src/android/connection-service.test.ts +0 -114
  16. package/src/callkit/resolve-space-callkit-options.test.ts +0 -68
  17. package/src/client.test.ts +0 -12
  18. package/src/clipboard.test.ts +0 -79
  19. package/src/components/Chalk.test.tsx +0 -223
  20. package/src/components/EmbeddedWhiteboard.test.tsx +0 -92
  21. package/src/components/Entrance.test.tsx +0 -26
  22. package/src/components/EntranceView.android.test.tsx +0 -100
  23. package/src/components/EntranceView.ios-pad.test.tsx +0 -160
  24. package/src/components/EntranceView.ios-phone.test.tsx +0 -137
  25. package/src/components/EntranceView.test.tsx +0 -15
  26. package/src/components/ReactionPicker.test.tsx +0 -19
  27. package/src/components/RtcVideoView.test.tsx +0 -12
  28. package/src/components/SpaceView.android.test.tsx +0 -11
  29. package/src/components/SpaceView.ios-pad.test.tsx +0 -11
  30. package/src/components/SpaceView.ios-phone.test.tsx +0 -11
  31. package/src/components/SpaceView.macos.test.tsx +0 -11
  32. package/src/components/SpaceView.shared.test.tsx +0 -116
  33. package/src/components/SpaceView.test.tsx +0 -15
  34. package/src/components/android-connection-service-controller.test.ts +0 -80
  35. package/src/components/episode-diagnostic-render-observer.test.ts +0 -24
  36. package/src/components/logo/Logo.test.tsx +0 -144
  37. package/src/components/native-animation-controller.test.ts +0 -47
  38. package/src/components/native-chat.test.ts +0 -22
  39. package/src/components/native-entrance/entrance-controller-store.test.ts +0 -75
  40. package/src/components/native-media-visibility.test.ts +0 -77
  41. package/src/components/native-space-callkit-controller.test.ts +0 -62
  42. package/src/components/native-space-view/AppearanceSettings.test.tsx +0 -56
  43. package/src/components/native-space-view/ParticipantActionSheet.test.tsx +0 -18
  44. package/src/components/native-space-view/SettingsSheet.test.tsx +0 -17
  45. package/src/components/native-space-view/SpaceActionMenu.test.tsx +0 -126
  46. package/src/components/native-space-view/SpaceBottomDock.android.test.tsx +0 -61
  47. package/src/components/native-space-view/SpaceChatSheet.test.tsx +0 -18
  48. package/src/components/native-space-view/SpaceMoreSheet.test.tsx +0 -15
  49. package/src/components/native-space-view/SpacePanelSheet.test.tsx +0 -15
  50. package/src/components/native-space-view/SpacePeopleSheet.test.tsx +0 -13
  51. package/src/components/native-space-view/SpaceSurfacePrimitives.test.tsx +0 -12
  52. package/src/components/native-space-view/SpaceTopBar.android.test.tsx +0 -50
  53. package/src/components/native-space-view/SpaceWhiteboardSurface.test.tsx +0 -85
  54. package/src/components/native-space-view/diagnostics.test.ts +0 -31
  55. package/src/components/native-space-view/format-space-duration.test.ts +0 -13
  56. package/src/components/native-space-view/native-space-grid-keys.test.ts +0 -11
  57. package/src/components/native-space-view/native-space-page-store.test.ts +0 -23
  58. package/src/components/native-space-view/space-chat-attachments.test.ts +0 -33
  59. package/src/components/native-space-view/space-progressive-surface-helpers.test.ts +0 -24
  60. package/src/components/native-space-view/useSpaceViewController.contract.test.ts +0 -10
  61. package/src/components/native-space-view/useSpaceViewController.test.ts +0 -106
  62. package/src/components/native-space-view/useSpaceViewDerived.test.ts +0 -58
  63. package/src/components/native-space-view/useSpaceViewPanels.controlled.test.ts +0 -40
  64. package/src/components/native-space-view/useSpaceViewPanels.test.ts +0 -73
  65. package/src/components/space-lifecycle.test.ts +0 -19
  66. package/src/context/space-client-context.test.tsx +0 -10
  67. package/src/diagnostics.test.ts +0 -29
  68. package/src/hooks/entrance-preview-store.test.ts +0 -79
  69. package/src/hooks/space-hooks.test.ts +0 -76
  70. package/src/hooks/useLayout.test.ts +0 -16
  71. package/src/invites.test.ts +0 -51
  72. package/src/public-surface.test.ts +0 -26
  73. package/src/runtime.test.ts +0 -83
  74. package/src/space-client/cloudflare-rtk-native.test.ts +0 -118
  75. package/src/space-client/create-native-space-client.test.ts +0 -300
  76. package/src/telemetry.test.ts +0 -143
  77. package/src/test-support/test-renderer.test.ts +0 -9
  78. package/src/test-support/test-renderer.ts +0 -28
  79. package/src/ui/appearance.test.ts +0 -61
  80. package/src/ui/native-appearance-context.controlled.test.tsx +0 -41
  81. package/src/ui/native-appearance-context.test.tsx +0 -14
  82. package/src/ui/native-theme.test.ts +0 -34
  83. package/src/ui/native-theme.test.tsx +0 -14
  84. package/src/ui/participant-avatar.test.ts +0 -29
  85. package/src/ui/theme-tokens.test.ts +0 -14
  86. package/src/ui/theme.test.ts +0 -36
  87. package/src/utils/build-chalk-invite-link.test.ts +0 -12
  88. package/src/utils/ios-simulator.test.ts +0 -96
  89. package/src/utils/native-space-layout.test.ts +0 -170
  90. package/src/whiteboard/embedded-whiteboard-assets.test.ts +0 -31
@@ -1,85 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- const nativeTheme = vi.hoisted(() => ({ colorScheme: "light" as "light" | "dark" }));
4
-
5
- vi.mock("react", () => ({
6
- useCallback: <T,>(callback: T) => callback,
7
- }));
8
- vi.mock("react-native", () => ({
9
- StyleSheet: { create: <Styles,>(styles: Styles) => styles },
10
- }));
11
- vi.mock("../../ui/native-theme", () => ({
12
- useNativeTheme: () => nativeTheme,
13
- }));
14
- vi.mock("../EmbeddedWhiteboard", () => ({
15
- EmbeddedWhiteboard: "EmbeddedWhiteboard",
16
- }));
17
-
18
- import type { SpaceWhiteboardState } from "./useSpaceViewPanels";
19
- import { forwardNativeSpaceWhiteboardFailure, forwardNativeSpaceWhiteboardMetric, shouldRenderNativeSpaceWhiteboard, SpaceWhiteboardSurface } from "./SpaceWhiteboardSurface";
20
-
21
- describe("SpaceWhiteboardSurface", () => {
22
- it("renders only for an open canonical whiteboard transport", () => {
23
- expect(shouldRenderNativeSpaceWhiteboard(controller({ isOpen: false, transport: null }))).toBe(false);
24
- expect(shouldRenderNativeSpaceWhiteboard(controller({ isOpen: true, transport: null }))).toBe(false);
25
- expect(
26
- shouldRenderNativeSpaceWhiteboard(
27
- controller({
28
- isOpen: true,
29
- transport: {} as NonNullable<SpaceWhiteboardState["transport"]>,
30
- }),
31
- ),
32
- ).toBe(true);
33
- });
34
-
35
- it("forwards renderer metrics in production runtimes", () => {
36
- const onMetric = vi.fn();
37
- const metric = { name: "whiteboard.frame.delay_ms", value: 17 };
38
-
39
- forwardNativeSpaceWhiteboardMetric(metric, onMetric);
40
-
41
- expect(onMetric).toHaveBeenCalledWith(metric);
42
- });
43
-
44
- it("forwards renderer failures without relying on development logging", () => {
45
- const onMetric = vi.fn();
46
- forwardNativeSpaceWhiteboardFailure({ code: "renderer_load_failed", recoverable: true }, onMetric);
47
- expect(onMetric).toHaveBeenCalledWith({
48
- name: "whiteboard.renderer.failure",
49
- value: 1,
50
- attributes: { code: "renderer_load_failed", recoverable: true },
51
- });
52
-
53
- const element = SpaceWhiteboardSurface({ whiteboard: controller({ isOpen: true, transport: {} as NonNullable<SpaceWhiteboardState["transport"]>, onMetric }) });
54
- element?.props.onError({ code: "renderer_stopped", message: "renderer stopped", recoverable: true });
55
- expect(onMetric).toHaveBeenCalledTimes(2);
56
- });
57
-
58
- it("passes the active NativeTheme color scheme to the embedded renderer", () => {
59
- nativeTheme.colorScheme = "light";
60
- const lightElement = SpaceWhiteboardSurface({ whiteboard: controller({ isOpen: true, transport: {} as NonNullable<SpaceWhiteboardState["transport"]> }) });
61
- expect(lightElement).toMatchObject({ props: { theme: "light" } });
62
-
63
- nativeTheme.colorScheme = "dark";
64
- const darkElement = SpaceWhiteboardSurface({ whiteboard: controller({ isOpen: true, transport: {} as NonNullable<SpaceWhiteboardState["transport"]> }) });
65
- expect(darkElement).toMatchObject({ props: { theme: "dark" } });
66
- });
67
- });
68
-
69
- function controller(overrides: Partial<SpaceWhiteboardState>): SpaceWhiteboardState {
70
- return {
71
- isOpen: false,
72
- canDraw: false,
73
- canClear: false,
74
- elements: [],
75
- openParticipants: [],
76
- transport: null,
77
- journeyId: "journey-test",
78
- open: vi.fn(),
79
- close: vi.fn(),
80
- toggle: vi.fn(),
81
- requestSync: vi.fn(),
82
- clear: vi.fn(),
83
- ...overrides,
84
- };
85
- }
@@ -1,31 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { buildSpaceViewDiagnosticsSnapshot } from "./diagnostics";
4
-
5
- describe("buildSpaceViewDiagnosticsSnapshot", () => {
6
- it("surfaces canonical feature and moderation availability", () => {
7
- const snapshot = buildSpaceViewDiagnosticsSnapshot({
8
- featureFlags: {
9
- chat: true,
10
- participants: true,
11
- screenShare: false,
12
- reactions: true,
13
- handRaise: true,
14
- whiteboard: true,
15
- },
16
- canEndEpisode: false,
17
- participantCount: 2,
18
- raisedHandCount: 0,
19
- unreadChatCount: 0,
20
- isScreenShareActive: false,
21
- isLocalScreenSharing: false,
22
- screenShareSharerParticipantId: null,
23
- });
24
-
25
- expect(snapshot.actionAvailability.screenShare).toMatchObject({
26
- enabled: false,
27
- reason: "feature-disabled",
28
- });
29
- expect(snapshot.actionAvailability.moderation.reason).toBe("capability-unavailable");
30
- });
31
- });
@@ -1,13 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { formatSpaceDuration } from "./format-space-duration";
4
-
5
- describe("formatSpaceDuration", () => {
6
- it.each([
7
- [0, "0:00"],
8
- [59, "0:59"],
9
- [125, "2:05"],
10
- ])("formats %s elapsed seconds", (secondsElapsed, expected) => {
11
- expect(formatSpaceDuration(secondsElapsed)).toBe(expected);
12
- });
13
- });
@@ -1,11 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { nativeSpaceGridKey } from "./native-space-grid-keys";
4
-
5
- describe("nativeSpaceGridKey", () => {
6
- it("changes when the ordered participant ids change and identifies an empty page", () => {
7
- expect(nativeSpaceGridKey("page", [{ id: "participant-a" }, { id: "participant-b" }] as never)).toBe("page-participant-a|participant-b");
8
- expect(nativeSpaceGridKey("page", [{ id: "participant-b" }, { id: "participant-a" }] as never)).toBe("page-participant-b|participant-a");
9
- expect(nativeSpaceGridKey("page", [])).toBe("page-empty");
10
- });
11
- });
@@ -1,23 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { createSpacePageStore } from "./native-space-page-store";
3
-
4
- describe("createSpacePageStore", () => {
5
- it("keeps a non-negative page and notifies subscribers when it changes", () => {
6
- const store = createSpacePageStore();
7
- const listener = vi.fn();
8
- const unsubscribe = store.subscribe(listener);
9
-
10
- store.setPage(2);
11
- store.setPage(2);
12
- store.clampToPageCount(1);
13
-
14
- expect(store.getSnapshot()).toBe(0);
15
- expect(listener).toHaveBeenCalledTimes(2);
16
-
17
- unsubscribe();
18
- store.setPage(3);
19
-
20
- expect(store.getSnapshot()).toBe(3);
21
- expect(listener).toHaveBeenCalledTimes(2);
22
- });
23
- });
@@ -1,33 +0,0 @@
1
- import { CHALK_CHAT_ATTACHMENT_LIMITS } from "@q9labsai/chalk-client";
2
- import { describe, expect, it, vi } from "vitest";
3
-
4
- import { normalizeChatFileDrafts, uploadAndSendNativeChatAttachments } from "./space-chat-attachments";
5
-
6
- describe("native Space chat attachments", () => {
7
- it("accepts lazy file readers and enforces the client bounds", () => {
8
- const result = normalizeChatFileDrafts([
9
- { name: "note.txt", type: "text/plain", size: 5, arrayBuffer: async () => new TextEncoder().encode("hello").buffer },
10
- { fileName: "image.png", mimeType: "image/png", bytes: new Uint8Array([1, 2]).buffer },
11
- ]);
12
-
13
- expect(result.error).toBeNull();
14
- expect(result.files.map((file) => file.fileName)).toEqual(["note.txt", "image.png"]);
15
- });
16
-
17
- it("rejects unsupported types and excess staged files before upload", () => {
18
- expect(normalizeChatFileDrafts([{ name: "script.js", type: "application/javascript", size: 1, arrayBuffer: async () => new ArrayBuffer(1) }])).toMatchObject({ files: [], error: "This file type is not supported in Space chat." });
19
- const files = Array.from({ length: CHALK_CHAT_ATTACHMENT_LIMITS.maximumPerMessage + 1 }, (_, index) => ({ name: `note-${index}.txt`, type: "text/plain", size: 1, arrayBuffer: async () => new ArrayBuffer(1) }));
20
- expect(normalizeChatFileDrafts(files)).toMatchObject({ files: [], error: `You can attach up to ${CHALK_CHAT_ATTACHMENT_LIMITS.maximumPerMessage} files per message.` });
21
- });
22
-
23
- it("uploads every staged file before sending an attachment-only message", async () => {
24
- const files = normalizeChatFileDrafts([{ name: "note.txt", type: "text/plain", size: 1, arrayBuffer: async () => new ArrayBuffer(1) }]).files;
25
- const upload = vi.fn(async () => ({ attachmentId: "note.txt", fileName: "note.txt", mimeType: "text/plain" as const, byteLength: 1 }));
26
- const send = vi.fn(async () => undefined);
27
-
28
- await uploadAndSendNativeChatAttachments(files, "", upload, send);
29
-
30
- expect(upload).toHaveBeenCalledOnce();
31
- expect(send).toHaveBeenCalledWith({ text: "", attachments: [{ attachmentId: "note.txt", fileName: "note.txt", mimeType: "text/plain", byteLength: 1 }] });
32
- });
33
- });
@@ -1,24 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { buildParticipantActionDescriptors, formatChatTimestamp } from "./space-progressive-surface-helpers";
4
-
5
- describe("space progressive surface helpers", () => {
6
- it("only exposes capability-backed Participant actions", () => {
7
- const actions = buildParticipantActionDescriptors({
8
- canMuteParticipants: true,
9
- canRequestMedia: false,
10
- canStopParticipantCamera: false,
11
- canStopParticipantScreenShare: false,
12
- canRemoveParticipants: true,
13
- });
14
-
15
- expect(actions.map((action) => action.label)).toEqual(["Mute microphone", "Remove Participant"]);
16
- expect(actions.at(-1)?.destructive).toBe(true);
17
- });
18
-
19
- it("formats valid chat timestamps and ignores empty metadata", () => {
20
- expect(formatChatTimestamp(0)).toBe("");
21
- expect(formatChatTimestamp(Number.NaN)).toBe("");
22
- expect(formatChatTimestamp(Date.UTC(2026, 7, 4, 12, 34))).toMatch(/\d{1,2}:34/);
23
- });
24
- });
@@ -1,10 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import type { SpaceViewControllerContractCheck } from "./useSpaceViewController.contract";
4
-
5
- describe("useSpaceViewController contract", () => {
6
- it("keeps the bidirectional return-shape assertion compiled", () => {
7
- const contractCheck: SpaceViewControllerContractCheck = true;
8
- expect(contractCheck).toBe(true);
9
- });
10
- });
@@ -1,106 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- const directClient = vi.hoisted(() => ({
4
- chat: { files: { url: vi.fn(() => "https://example.test/file") }, loadOlder: vi.fn(async () => ({ status: "loaded" })), markRead: vi.fn(async () => null), send: vi.fn(async () => ({})) },
5
- media: {
6
- acceptRequest: vi.fn(async () => undefined),
7
- declineRequest: vi.fn(async () => undefined),
8
- setCameraEnabled: vi.fn(async () => undefined),
9
- setMicrophoneEnabled: vi.fn(async () => undefined),
10
- setScreenShareEnabled: vi.fn(async () => undefined),
11
- },
12
- participants: {
13
- admit: vi.fn(async () => undefined),
14
- assignRole: vi.fn(async () => undefined),
15
- deny: vi.fn(async () => undefined),
16
- lowerHand: vi.fn(async () => undefined),
17
- mute: vi.fn(async () => undefined),
18
- raiseHand: vi.fn(async () => undefined),
19
- remove: vi.fn(async () => undefined),
20
- renameSelf: vi.fn(async () => undefined),
21
- requestMedia: vi.fn(async () => ({ status: "delivered" })),
22
- stopScreenShare: vi.fn(async () => undefined),
23
- stopVideo: vi.fn(async () => undefined),
24
- },
25
- reactions: { send: vi.fn(async () => ({})) },
26
- whiteboard: { transport: vi.fn(() => null) },
27
- }));
28
-
29
- vi.mock("react", () => ({
30
- useCallback: <T>(callback: T) => callback,
31
- useEffect: () => undefined,
32
- useMemo: <T>(factory: () => T) => factory(),
33
- useState: <T>(initial: T): readonly [T, (next: T) => void] => [initial, vi.fn()],
34
- }));
35
- vi.mock("react-native", () => ({ Alert: { alert: vi.fn() }, Linking: { openURL: vi.fn(async () => undefined) } }));
36
- vi.mock("../../context/space-client-context", () => ({ useSpaceClient: () => directClient }));
37
- vi.mock("../../hooks/space-hooks", () => ({
38
- useSelf: () => ({ participantId: "self", displayName: "Taylor", handRaised: false, can: (capability: string) => capability !== "endEpisode" }),
39
- useParticipants: () => ({
40
- roster: [
41
- {
42
- participantId: "self",
43
- displayName: "Taylor",
44
- role: "collaborator",
45
- eligibleRoles: [],
46
- capabilities: [],
47
- handRaised: false,
48
- media: { microphone: "active", camera: "active", screenShare: "inactive" },
49
- presence: { state: "connected", speaking: false, activeSpeaker: false },
50
- },
51
- ],
52
- admissionQueue: [],
53
- }),
54
- useMedia: () => ({
55
- local: {
56
- microphone: { state: "enabled", track: null },
57
- camera: { state: "enabled", track: null },
58
- screen: { state: "disabled", track: null },
59
- },
60
- screenShare: { state: "disabled", track: null },
61
- remote: [],
62
- incomingRequests: [],
63
- }),
64
- useChat: () => ({ status: "ready", messages: [], pendingSends: [], readReceipts: [], unreadCount: 0, pagination: { cursor: null, hasOlder: false, historyTruncated: false } }),
65
- useReactions: () => ({ active: [] }),
66
- useWhiteboard: () => ({ engine: { status: "ready", sceneId: null, revision: null, error: null } }),
67
- }));
68
- vi.mock("../../utils/ios-simulator", () => ({ isIosSimulator: () => false }));
69
- vi.mock("./useSpaceViewPanels", () => ({
70
- useSpaceViewPanels: () => ({
71
- layout: { layout: "grid" },
72
- panel: null,
73
- secondsElapsed: 5,
74
- formattedDuration: "0:05",
75
- actionsOpen: false,
76
- reactionPickerOpen: false,
77
- whiteboard: { isOpen: false, canDraw: true, canClear: false, elements: [], openParticipants: [], transport: null },
78
- handleLeave: vi.fn(),
79
- openPanel: vi.fn(),
80
- closePanel: vi.fn(),
81
- handleInviteParticipants: vi.fn(),
82
- }),
83
- }));
84
- vi.mock("./useSpaceViewDerived", () => ({ useSpaceViewDerived: () => ({ isStageMode: false, gridPages: [], allParticipants: [] }) }));
85
-
86
- import { useSpaceViewController } from "./useSpaceViewController";
87
-
88
- describe("useSpaceViewController", () => {
89
- it("binds native Space actions directly to the canonical SpaceClient", async () => {
90
- const controller = useSpaceViewController({ spaceName: "Design review", onLeave: vi.fn() });
91
-
92
- expect(controller.participants.localParticipant?.displayName).toBe("Taylor");
93
- expect(controller.chat.isEnabled).toBe(true);
94
- expect(controller.canSettings).toBe(true);
95
- expect(controller.canInvite).toBe(false);
96
- controller.toggleAudio();
97
- controller.sendReaction("🎉");
98
- controller.requestStartParticipantCamera("participant-2");
99
-
100
- await vi.waitFor(() => {
101
- expect(directClient.media.setMicrophoneEnabled).toHaveBeenCalledWith(false);
102
- expect(directClient.reactions.send).toHaveBeenCalledWith("🎉");
103
- expect(directClient.participants.requestMedia).toHaveBeenCalledWith("participant-2", "camera");
104
- });
105
- });
106
- });
@@ -1,58 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const viewport = vi.hoisted(() => ({ width: 500 }));
4
- const defaultViewportWidth = 500;
5
-
6
- vi.mock("react", () => ({
7
- useMemo: <T>(factory: () => T) => factory(),
8
- }));
9
- vi.mock("react-native", () => ({
10
- useWindowDimensions: () => ({ width: viewport.width, height: 800 }),
11
- }));
12
-
13
- import { useSpaceViewDerived } from "./useSpaceViewDerived";
14
-
15
- describe("useSpaceViewDerived", () => {
16
- beforeEach(() => {
17
- viewport.width = defaultViewportWidth;
18
- });
19
-
20
- afterEach(() => {
21
- viewport.width = defaultViewportWidth;
22
- });
23
-
24
- it("marks compact whiteboard layouts and keeps the local participant first", () => {
25
- const local = { id: "local", screenShareTrack: null };
26
- const remote = { id: "remote", screenShareTrack: null };
27
-
28
- const derived = useSpaceViewDerived({
29
- participants: [remote] as never,
30
- localParticipant: local as never,
31
- screenShare: { isActive: false, isLocalSharing: false, sharerParticipantId: null, videoTrack: null },
32
- isWhiteboardOpen: true,
33
- });
34
-
35
- expect(derived.isCompactViewport).toBe(true);
36
- expect(derived.primaryContent).toBe("whiteboard");
37
- expect(derived.allParticipants.map((participant) => participant.id)).toEqual(["local", "remote"]);
38
- });
39
-
40
- it("uses the wide viewport to expose an active remote screen share", () => {
41
- viewport.width = 1024;
42
- const local = { id: "local", screenShareTrack: null };
43
- const remote = { id: "remote", screenShareTrack: null };
44
- const track = { readyState: "live" } as never;
45
-
46
- const derived = useSpaceViewDerived({
47
- participants: [remote] as never,
48
- localParticipant: local as never,
49
- screenShare: { isActive: true, isLocalSharing: false, sharerParticipantId: "remote", videoTrack: track },
50
- isWhiteboardOpen: false,
51
- });
52
-
53
- expect(derived.isCompactViewport).toBe(false);
54
- expect(derived.primaryContent).toBe("screen-share");
55
- expect(derived.screenSharer).toBe(remote);
56
- expect(derived.screenShareTrack).toBe(track);
57
- });
58
- });
@@ -1,40 +0,0 @@
1
- // @vitest-environment happy-dom
2
-
3
- import { describe, expect, it, vi } from "vitest";
4
-
5
- import { act, renderHook } from "../../test-support/test-renderer";
6
-
7
- const alert = vi.hoisted(() => ({ alert: vi.fn() }));
8
-
9
- vi.mock("react-native", () => ({ Alert: alert, Share: { share: vi.fn(async () => undefined) } }));
10
-
11
- import { useSpaceViewPanels } from "./useSpaceViewPanels";
12
-
13
- describe("controlled Space view fixture state", () => {
14
- it("updates production panel and whiteboard state without remounting the hook", () => {
15
- const base = {
16
- canWhiteboard: true,
17
- canDraw: true,
18
- canClear: false,
19
- canEndEpisode: false,
20
- transport: null,
21
- onLeave: vi.fn(),
22
- onEndEpisode: undefined,
23
- run: vi.fn(async (action: () => void | Promise<void>) => {
24
- await action();
25
- }),
26
- };
27
- const { result, rerender } = renderHook(({ state }) => useSpaceViewPanels({ ...base, controlledState: state }), { initialProps: { state: { layout: "grid" as const, panel: null, settingsOpen: false, whiteboardOpen: false } } });
28
-
29
- expect(result.current.layout.layout).toBe("grid");
30
- expect(result.current.panel).toBeNull();
31
- expect(result.current.whiteboard.isOpen).toBe(false);
32
-
33
- act(() => rerender({ state: { layout: "presentation", panel: "participants", settingsOpen: true, whiteboardOpen: true } }));
34
-
35
- expect(result.current.layout.layout).toBe("presentation");
36
- expect(result.current.panel).toBe("participants");
37
- expect(result.current.settingsOpen).toBe(true);
38
- expect(result.current.whiteboard.isOpen).toBe(true);
39
- });
40
- });
@@ -1,73 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- const alert = vi.hoisted(() => ({ alert: vi.fn() }));
4
-
5
- vi.mock("react", () => ({
6
- useCallback: <T>(callback: T) => callback,
7
- useEffect: () => undefined,
8
- useMemo: <T>(factory: () => T) => factory(),
9
- useRef: <T>(current: T) => ({ current }),
10
- useState: <T>(initial: T): readonly [T, (next: T) => void] => [initial, vi.fn()],
11
- }));
12
- vi.mock("react-native", () => ({ Alert: alert, Share: { share: vi.fn(async () => undefined) } }));
13
- vi.mock("../../hooks/useLayout", () => ({
14
- useLayout: () => ({ layout: "grid", isMobileView: true, isFullscreen: false, setLayout: vi.fn(), toggleLayout: vi.fn(), toggleFullscreen: vi.fn(async () => undefined) }),
15
- }));
16
-
17
- import { useSpaceViewPanels } from "./useSpaceViewPanels";
18
-
19
- describe("useSpaceViewPanels", () => {
20
- it("composes layout, panel state, whiteboard state, and leave actions", () => {
21
- const panels = useSpaceViewPanels({
22
- spaceName: "Space",
23
- inviteLink: "https://example.test/space",
24
- canWhiteboard: true,
25
- canDraw: true,
26
- canClear: false,
27
- canEndEpisode: true,
28
- transport: null,
29
- onLeave: vi.fn(),
30
- onEndEpisode: vi.fn(),
31
- run: vi.fn(async (action) => {
32
- await action();
33
- }),
34
- });
35
-
36
- expect(panels).toMatchObject({ layout: { layout: "grid" }, panel: null, formattedDuration: "0:00", whiteboard: { isOpen: false, canDraw: true } });
37
- expect(panels.whiteboard.journeyId).toMatch(/^native-whiteboard-|^[0-9a-f-]{36}$/u);
38
- expect(panels.whiteboard.onMetric).toBeUndefined();
39
- panels.openPanel("participants");
40
- expect(panels.openPanel).toBeTypeOf("function");
41
- panels.handleLeave();
42
- expect(alert.alert).toHaveBeenCalledWith("Leave Space?", "Choose how you want to leave.", expect.any(Array));
43
- });
44
-
45
- it("forwards registered W3C journey context to the whiteboard surface", () => {
46
- const onMetric = vi.fn();
47
- const panels = useSpaceViewPanels({
48
- canWhiteboard: true,
49
- canDraw: true,
50
- canClear: false,
51
- canEndEpisode: false,
52
- transport: null,
53
- onLeave: vi.fn(),
54
- onEndEpisode: undefined,
55
- run: vi.fn(async (action) => {
56
- await action();
57
- }),
58
- journeyContext: {
59
- journeyId: "00000000-0000-4000-8000-000000000001",
60
- traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
61
- tracestate: "chalk=mobile",
62
- recordWhiteboardMetric: onMetric,
63
- },
64
- });
65
-
66
- expect(panels.whiteboard).toMatchObject({
67
- journeyId: "00000000-0000-4000-8000-000000000001",
68
- traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
69
- tracestate: "chalk=mobile",
70
- onMetric,
71
- });
72
- });
73
- });
@@ -1,19 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { fileURLToPath } from "node:url";
3
- import ts from "typescript";
4
- import { describe, expect, it } from "vitest";
5
-
6
- describe("SpaceLifecyclePhase", () => {
7
- it("keeps the exported source contract on the canonical native phases", () => {
8
- const sourcePath = fileURLToPath(new URL("./space-lifecycle.ts", import.meta.url));
9
- const sourceFile = ts.createSourceFile("space-lifecycle.ts", readFileSync(sourcePath, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
10
- const declaration = sourceFile.statements.find((statement): statement is ts.TypeAliasDeclaration => ts.isTypeAliasDeclaration(statement) && statement.name.text === "SpaceLifecyclePhase");
11
-
12
- expect(declaration).toBeDefined();
13
- if (!declaration || !ts.isUnionTypeNode(declaration.type)) throw new Error("SpaceLifecyclePhase must remain an exported string union.");
14
-
15
- expect(declaration.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)).toBe(true);
16
- const phases = declaration.type.types.map((member) => (ts.isLiteralTypeNode(member) && ts.isStringLiteral(member.literal) ? member.literal.text : "<invalid>"));
17
- expect(phases).toEqual(["entrance", "joining", "live", "left"]);
18
- });
19
- });
@@ -1,10 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { ChalkProvider, useSpaceClient } from "./space-client-context";
4
-
5
- describe("ChalkProvider", () => {
6
- it("provides the canonical SpaceClient context entry", () => {
7
- expect(ChalkProvider).toBeTypeOf("function");
8
- expect(useSpaceClient).toBeTypeOf("function");
9
- });
10
- });
@@ -1,29 +0,0 @@
1
- import { beforeEach, describe, expect, it } from "vitest";
2
-
3
- import { buildDevDiagnosticsCopyText, classifyTarget, getDevDiagnosticsState, recordDiagnosticsFailure, resetDevDiagnosticsState, setDevDiagnosticsEnvironment } from "./diagnostics";
4
-
5
- describe("native diagnostics", () => {
6
- beforeEach(resetDevDiagnosticsState);
7
-
8
- it("does not retain caller credentials", () => {
9
- expect(buildDevDiagnosticsCopyText()).not.toContain("inviteToken");
10
- });
11
-
12
- it("uses the public-invite API as the single environment boundary", () => {
13
- setDevDiagnosticsEnvironment({
14
- apiBaseURL: "http://localhost:8787",
15
- });
16
- expect(getDevDiagnosticsState().environment.target).toBe("local");
17
- expect(classifyTarget("https://api.chalkmeet.com")).toBe("production");
18
- });
19
-
20
- it("records failures without legacy auth or transport state", () => {
21
- recordDiagnosticsFailure("connection-join", "Sync unavailable");
22
- expect(getDevDiagnosticsState().lastFailure).toMatchObject({
23
- source: "connection-join",
24
- message: "Sync unavailable",
25
- });
26
- expect(buildDevDiagnosticsCopyText()).not.toContain("apiKey");
27
- expect(buildDevDiagnosticsCopyText()).not.toContain("RealtimeKit");
28
- });
29
- });
@@ -1,79 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import type { NativeMediaStream, NativeMediaStreamTrack } from "../media/native-webrtc";
3
- import { createEntrancePreviewStore } from "./entrance-preview-store";
4
-
5
- function createStream(withVideoTrack: boolean): { stream: NativeMediaStream; wasStopped: () => boolean } {
6
- let stopped = false;
7
- const track: NativeMediaStreamTrack = {
8
- id: "camera-track",
9
- kind: "video",
10
- enabled: true,
11
- muted: false,
12
- readyState: "live",
13
- stop: () => {
14
- stopped = true;
15
- },
16
- };
17
- const stream: NativeMediaStream = {
18
- toURL: () => "camera-stream",
19
- getTracks: () => (withVideoTrack ? [track] : []),
20
- getVideoTracks: () => (withVideoTrack ? [track] : []),
21
- getAudioTracks: () => [],
22
- };
23
-
24
- return { stream, wasStopped: () => stopped };
25
- }
26
-
27
- describe("createEntrancePreviewStore", () => {
28
- it("starts the camera on subscription and stops it on cleanup", async () => {
29
- const camera = createStream(true);
30
- const getUserMedia = vi.fn(async () => camera.stream);
31
- const store = createEntrancePreviewStore({
32
- enabled: true,
33
- simulatorVideoDisabled: false,
34
- simulatorVideoMessage: "simulator",
35
- getUserMedia,
36
- });
37
-
38
- const unsubscribe = store.subscribe(() => {});
39
- await Promise.resolve();
40
-
41
- expect(getUserMedia).toHaveBeenCalledWith({ audio: false, video: { facingMode: "user" } });
42
- expect(store.getSnapshot()).toEqual({ previewStream: camera.stream, previewError: null });
43
-
44
- unsubscribe();
45
- expect(camera.wasStopped()).toBe(true);
46
- });
47
-
48
- it("reports simulator video as unavailable without opening the camera", () => {
49
- const getUserMedia = vi.fn(async () => createStream(true).stream);
50
- const store = createEntrancePreviewStore({
51
- enabled: true,
52
- simulatorVideoDisabled: true,
53
- simulatorVideoMessage: "Video is unavailable in the iOS simulator",
54
- getUserMedia,
55
- });
56
-
57
- const unsubscribe = store.subscribe(() => {});
58
-
59
- expect(store.getSnapshot()).toEqual({ previewStream: null, previewError: "Video is unavailable in the iOS simulator" });
60
- expect(getUserMedia).not.toHaveBeenCalled();
61
- unsubscribe();
62
- });
63
-
64
- it("keeps the preview disabled when Entrance disables preview", () => {
65
- const getUserMedia = vi.fn(async () => createStream(true).stream);
66
- const store = createEntrancePreviewStore({
67
- enabled: false,
68
- simulatorVideoDisabled: false,
69
- simulatorVideoMessage: "simulator",
70
- getUserMedia,
71
- });
72
-
73
- const unsubscribe = store.subscribe(() => {});
74
-
75
- expect(store.getSnapshot()).toEqual({ previewStream: null, previewError: null });
76
- expect(getUserMedia).not.toHaveBeenCalled();
77
- unsubscribe();
78
- });
79
- });