@q9labsai/chalk-react-native 4.1.11 → 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.
Files changed (103) hide show
  1. package/dist/android.js +4 -2
  2. package/dist/{chunk-MMBE2BVK.js → chunk-4NLA35HE.js} +649 -407
  3. package/dist/{chunk-LZ67SXGB.js → chunk-MK6OO4V7.js} +6 -0
  4. package/dist/client.js +1 -1
  5. package/dist/components/Entrance.d.ts.map +1 -1
  6. package/dist/components/logo/Logo.d.ts +11 -0
  7. package/dist/components/logo/Logo.d.ts.map +1 -0
  8. package/dist/components/native-space-view/SpaceTopBar.android.d.ts.map +1 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -2
  12. package/dist/ios-pad.js +4 -2
  13. package/dist/ios-phone.js +4 -2
  14. package/dist/ios.js +4 -2
  15. package/dist/macos.js +4 -2
  16. package/package.json +4 -4
  17. package/src/callkit/callkit.test.ts +12 -10
  18. package/src/components/Entrance.tsx +2 -1
  19. package/src/components/logo/Logo.tsx +309 -0
  20. package/src/components/native-space-view/SpaceTopBar.android.tsx +2 -1
  21. package/src/index.ts +2 -0
  22. package/src/space-client/create-native-space-client.ts +8 -0
  23. package/dist/components/LogoElements.d.ts +0 -6
  24. package/dist/components/LogoElements.d.ts.map +0 -1
  25. package/dist/test-support/test-renderer.d.ts +0 -20
  26. package/dist/test-support/test-renderer.d.ts.map +0 -1
  27. package/src/android/connection-service.test.ts +0 -114
  28. package/src/callkit/resolve-space-callkit-options.test.ts +0 -68
  29. package/src/client.test.ts +0 -12
  30. package/src/clipboard.test.ts +0 -79
  31. package/src/components/Chalk.test.tsx +0 -223
  32. package/src/components/EmbeddedWhiteboard.test.tsx +0 -92
  33. package/src/components/Entrance.test.tsx +0 -19
  34. package/src/components/EntranceView.android.test.tsx +0 -100
  35. package/src/components/EntranceView.ios-pad.test.tsx +0 -160
  36. package/src/components/EntranceView.ios-phone.test.tsx +0 -137
  37. package/src/components/EntranceView.test.tsx +0 -15
  38. package/src/components/LogoElements.test.tsx +0 -60
  39. package/src/components/LogoElements.tsx +0 -119
  40. package/src/components/ReactionPicker.test.tsx +0 -19
  41. package/src/components/RtcVideoView.test.tsx +0 -12
  42. package/src/components/SpaceView.android.test.tsx +0 -11
  43. package/src/components/SpaceView.ios-pad.test.tsx +0 -11
  44. package/src/components/SpaceView.ios-phone.test.tsx +0 -11
  45. package/src/components/SpaceView.macos.test.tsx +0 -11
  46. package/src/components/SpaceView.shared.test.tsx +0 -116
  47. package/src/components/SpaceView.test.tsx +0 -15
  48. package/src/components/android-connection-service-controller.test.ts +0 -80
  49. package/src/components/episode-diagnostic-render-observer.test.ts +0 -24
  50. package/src/components/native-animation-controller.test.ts +0 -47
  51. package/src/components/native-chat.test.ts +0 -22
  52. package/src/components/native-entrance/entrance-controller-store.test.ts +0 -75
  53. package/src/components/native-media-visibility.test.ts +0 -77
  54. package/src/components/native-space-callkit-controller.test.ts +0 -62
  55. package/src/components/native-space-view/AppearanceSettings.test.tsx +0 -56
  56. package/src/components/native-space-view/ParticipantActionSheet.test.tsx +0 -18
  57. package/src/components/native-space-view/SettingsSheet.test.tsx +0 -17
  58. package/src/components/native-space-view/SpaceActionMenu.test.tsx +0 -126
  59. package/src/components/native-space-view/SpaceBottomDock.android.test.tsx +0 -61
  60. package/src/components/native-space-view/SpaceChatSheet.test.tsx +0 -18
  61. package/src/components/native-space-view/SpaceMoreSheet.test.tsx +0 -15
  62. package/src/components/native-space-view/SpacePanelSheet.test.tsx +0 -15
  63. package/src/components/native-space-view/SpacePeopleSheet.test.tsx +0 -13
  64. package/src/components/native-space-view/SpaceSurfacePrimitives.test.tsx +0 -12
  65. package/src/components/native-space-view/SpaceTopBar.android.test.tsx +0 -43
  66. package/src/components/native-space-view/SpaceWhiteboardSurface.test.tsx +0 -85
  67. package/src/components/native-space-view/diagnostics.test.ts +0 -31
  68. package/src/components/native-space-view/format-space-duration.test.ts +0 -13
  69. package/src/components/native-space-view/native-space-grid-keys.test.ts +0 -11
  70. package/src/components/native-space-view/native-space-page-store.test.ts +0 -23
  71. package/src/components/native-space-view/space-chat-attachments.test.ts +0 -33
  72. package/src/components/native-space-view/space-progressive-surface-helpers.test.ts +0 -24
  73. package/src/components/native-space-view/useSpaceViewController.contract.test.ts +0 -10
  74. package/src/components/native-space-view/useSpaceViewController.test.ts +0 -106
  75. package/src/components/native-space-view/useSpaceViewDerived.test.ts +0 -58
  76. package/src/components/native-space-view/useSpaceViewPanels.controlled.test.ts +0 -40
  77. package/src/components/native-space-view/useSpaceViewPanels.test.ts +0 -73
  78. package/src/components/space-lifecycle.test.ts +0 -19
  79. package/src/context/space-client-context.test.tsx +0 -10
  80. package/src/diagnostics.test.ts +0 -29
  81. package/src/hooks/entrance-preview-store.test.ts +0 -79
  82. package/src/hooks/space-hooks.test.ts +0 -76
  83. package/src/hooks/useLayout.test.ts +0 -16
  84. package/src/invites.test.ts +0 -51
  85. package/src/public-surface.test.ts +0 -26
  86. package/src/runtime.test.ts +0 -83
  87. package/src/space-client/cloudflare-rtk-native.test.ts +0 -118
  88. package/src/space-client/create-native-space-client.test.ts +0 -300
  89. package/src/telemetry.test.ts +0 -143
  90. package/src/test-support/test-renderer.test.ts +0 -9
  91. package/src/test-support/test-renderer.ts +0 -28
  92. package/src/ui/appearance.test.ts +0 -61
  93. package/src/ui/native-appearance-context.controlled.test.tsx +0 -41
  94. package/src/ui/native-appearance-context.test.tsx +0 -14
  95. package/src/ui/native-theme.test.ts +0 -34
  96. package/src/ui/native-theme.test.tsx +0 -14
  97. package/src/ui/participant-avatar.test.ts +0 -29
  98. package/src/ui/theme-tokens.test.ts +0 -14
  99. package/src/ui/theme.test.ts +0 -36
  100. package/src/utils/build-chalk-invite-link.test.ts +0 -12
  101. package/src/utils/ios-simulator.test.ts +0 -96
  102. package/src/utils/native-space-layout.test.ts +0 -170
  103. package/src/whiteboard/embedded-whiteboard-assets.test.ts +0 -31
@@ -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
- });
@@ -1,76 +0,0 @@
1
- import type { SpaceClient, SpaceSnapshot } from "@q9labsai/chalk-client";
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
-
4
- const state = vi.hoisted(() => ({ client: null as SpaceClient | null }));
5
-
6
- vi.mock("../context/space-client-context", () => ({
7
- useSpaceClient: () => {
8
- if (!state.client) throw new Error("missing client");
9
- return state.client;
10
- },
11
- }));
12
-
13
- vi.mock("react", async (importOriginal) => {
14
- const react = await importOriginal<typeof import("react")>();
15
- return {
16
- ...react,
17
- useCallback: <T>(callback: T) => callback,
18
- useSyncExternalStore: (_subscribe: unknown, getSnapshot: () => unknown) => getSnapshot(),
19
- };
20
- });
21
-
22
- import { useCan, useChat, useConnection, useMedia, useParticipants, useReactions, useSelf, useWhiteboard } from "./space-hooks";
23
-
24
- describe("SpaceSnapshot hooks", () => {
25
- beforeEach(() => {
26
- state.client = client(snapshot());
27
- });
28
-
29
- it("returns the exact stable slices from SpaceSnapshot", () => {
30
- const current = state.client!.getSnapshot();
31
-
32
- expect(useConnection()).toBe(current.connection);
33
- expect(useSelf()).toBe(current.self);
34
- expect(useParticipants()).toBe(current.participants);
35
- expect(useMedia()).toBe(current.media);
36
- expect(useChat()).toBe(current.chat);
37
- expect(useReactions()).toBe(current.reactions);
38
- expect(useWhiteboard()).toBe(current.whiteboard);
39
- });
40
-
41
- it("derives capabilities from the self slice", () => {
42
- expect(useCan("sendChat")).toBe(true);
43
- expect(useCan("endEpisode")).toBe(false);
44
- });
45
- });
46
-
47
- function client(current: SpaceSnapshot): SpaceClient {
48
- return {
49
- getSnapshot: () => current,
50
- subscribe: () => () => undefined,
51
- } as SpaceClient;
52
- }
53
-
54
- function snapshot(): SpaceSnapshot {
55
- const capabilities = ["sendChat"] as const;
56
- return {
57
- connection: { status: "live", episode: null, lastError: null },
58
- self: { participantId: "participant-1", displayName: "Ada", role: "collaborator", capabilities, handRaised: false, can: (capability) => capabilities.includes(capability as (typeof capabilities)[number]) },
59
- participants: { roster: [], admissionQueue: [] },
60
- media: {
61
- devices: { microphones: [], cameras: [], speakers: [] },
62
- selection: { microphone: null, camera: null, speaker: null },
63
- local: {
64
- microphone: { source: "microphone", state: "disabled", track: null },
65
- camera: { source: "camera", state: "disabled", track: null },
66
- screen: { source: "screen", state: "disabled", track: null },
67
- },
68
- remote: [],
69
- screenShare: { source: "screen", state: "disabled", track: null },
70
- incomingRequests: [],
71
- },
72
- chat: { status: "idle", messages: [], pendingSends: [], readReceipts: [], unreadCount: 0, pagination: { cursor: null, hasOlder: false, historyTruncated: false }, lastError: null },
73
- reactions: { active: [] },
74
- whiteboard: { open: false, engine: { status: "unsubscribed", sceneId: null, revision: null, presenting: false, error: null } },
75
- };
76
- }
@@ -1,16 +0,0 @@
1
- // @vitest-environment happy-dom
2
-
3
- import { describe, expect, it } from "vitest";
4
-
5
- import { act, renderHook } from "../test-support/test-renderer";
6
- import { useLayout } from "./useLayout";
7
-
8
- describe("useLayout", () => {
9
- it("synchronizes a controlled layout without replacing the hook", () => {
10
- const { result, rerender } = renderHook(({ layout }) => useLayout({ layout }), { initialProps: { layout: "grid" as const } });
11
-
12
- expect(result.current.layout).toBe("grid");
13
- act(() => rerender({ layout: "presentation" }));
14
- expect(result.current.layout).toBe("presentation");
15
- });
16
- });
@@ -1,51 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { getClipboardInviteSuggestion, parseSpaceInviteLink } from "./invites";
3
-
4
- describe("parseSpaceInviteLink", () => {
5
- const inviteToken = "cspi1.key.payload.signature";
6
-
7
- it("accepts Chalk https invite links", () => {
8
- expect(parseSpaceInviteLink(`https://chalk.q9labs.ai/space/design-lab#spaceInviteToken=${inviteToken}`)).toEqual({ slug: "design-lab", spaceInviteToken: inviteToken });
9
- });
10
-
11
- it("accepts bare Chalk hostnames", () => {
12
- expect(parseSpaceInviteLink(`chalkmeet.com/space/design-lab#spaceInviteToken=${inviteToken}`)).toEqual({ slug: "design-lab", spaceInviteToken: inviteToken });
13
- });
14
-
15
- it("accepts native Chalk deep links with the canonical path", () => {
16
- expect(parseSpaceInviteLink(`chalk://space/design-lab#spaceInviteToken=${inviteToken}`)).toEqual({ slug: "design-lab", spaceInviteToken: inviteToken });
17
- expect(parseSpaceInviteLink(`ai.q9labs.chalk.mobile://space/design-lab#spaceInviteToken=${inviteToken}`)).toEqual({ slug: "design-lab", spaceInviteToken: inviteToken });
18
- });
19
-
20
- it("accepts loopback HTTP invite links for local development", () => {
21
- expect(parseSpaceInviteLink(`http://127.0.0.1:3070/space/design-lab#spaceInviteToken=${inviteToken}`)).toEqual({ slug: "design-lab", spaceInviteToken: inviteToken });
22
- expect(parseSpaceInviteLink(`http://localhost:3070/space/design-lab#spaceInviteToken=${inviteToken}`)).toEqual({ slug: "design-lab", spaceInviteToken: inviteToken });
23
- });
24
-
25
- it("rejects legacy paths, missing slugs, and invalid tokens", () => {
26
- expect(parseSpaceInviteLink(`https://chalk.q9labs.ai/legacy/${inviteToken}`)).toBeNull();
27
- expect(parseSpaceInviteLink(`https://chalk.q9labs.ai/space#spaceInviteToken=${inviteToken}`)).toBeNull();
28
- expect(parseSpaceInviteLink("https://chalk.q9labs.ai/space/design-lab#invite=legacy")).toBeNull();
29
- expect(parseSpaceInviteLink("https://attacker.test/space/design-lab#spaceInviteToken=cspi1.key.payload.signature")).toBeNull();
30
- expect(parseSpaceInviteLink(`http://chalkmeet.com/space/design-lab#spaceInviteToken=${inviteToken}`)).toBeNull();
31
- expect(parseSpaceInviteLink(`http://192.168.1.10/space/design-lab#spaceInviteToken=${inviteToken}`)).toBeNull();
32
- expect(parseSpaceInviteLink("https://chalk.q9labs.ai/space/design-lab#spaceInviteToken=ABC123")).toBeNull();
33
- expect(parseSpaceInviteLink(`https://chalk.q9labs.ai/space/design-lab#spaceInviteToken=${`cspi1.${"k".repeat(170)}.${"p".repeat(170)}.${"s".repeat(170)}`}`)).toBeNull();
34
- });
35
- });
36
-
37
- describe("getClipboardInviteSuggestion", () => {
38
- const inviteLink = "https://chalkmeet.com/space/design-lab#spaceInviteToken=cspi1.key.payload.signature";
39
-
40
- it("returns a copied Chalk invite link when it differs from the current input", () => {
41
- expect(getClipboardInviteSuggestion(inviteLink, "")).toBe(inviteLink);
42
- });
43
-
44
- it("hides the suggestion when the input already matches the clipboard", () => {
45
- expect(getClipboardInviteSuggestion(inviteLink, inviteLink)).toBeNull();
46
- });
47
-
48
- it("ignores non-invite clipboard values", () => {
49
- expect(getClipboardInviteSuggestion("ABC123", "")).toBeNull();
50
- });
51
- });
@@ -1,26 +0,0 @@
1
- import type { ClientEventMap } from "@q9labsai/chalk-client";
2
- import type { ThemeAppearance as ReactThemeAppearance, ThemePalette as ReactThemePalette, ThemeTexture as ReactThemeTexture } from "../../react/src/components/theme";
3
- import type { ChalkProps, ChalkThemeTokens, SpaceLayout } from "./components/Chalk";
4
- import type { EntranceProps, EntranceSettings } from "./components/Entrance";
5
- import type { ThemeAppearance, ThemePalette, ThemeTexture } from "./ui/appearance";
6
- import { expectTypeOf, test } from "vitest";
7
-
8
- test("the turnkey and entrance props use the ratified names", () => {
9
- expectTypeOf<ChalkProps>().toMatchTypeOf<{
10
- readonly entrance?: boolean;
11
- readonly defaults?: { readonly microphone?: boolean; readonly camera?: boolean };
12
- readonly features?: object;
13
- readonly layout?: SpaceLayout;
14
- readonly onOpenDiagnostics?: () => void;
15
- }>();
16
- expectTypeOf<EntranceProps>().toMatchTypeOf<{
17
- readonly defaults?: { readonly microphone?: boolean; readonly camera?: boolean };
18
- readonly onCancel?: () => void;
19
- }>();
20
- expectTypeOf<EntranceSettings>().toEqualTypeOf<{ readonly displayName: string; readonly microphone: boolean; readonly camera: boolean }>();
21
- expectTypeOf<NonNullable<ChalkProps["onError"]>>().toEqualTypeOf<(event: ClientEventMap["error"]) => void>();
22
- expectTypeOf<keyof ChalkThemeTokens>().toEqualTypeOf<"canvas" | "chrome" | "surface" | "stage" | "text" | "mutedText" | "line" | "accent" | "accentText" | "positive" | "danger" | "dangerSurface" | "focus" | "shadow">();
23
- expectTypeOf<ThemePalette>().toEqualTypeOf<ReactThemePalette>();
24
- expectTypeOf<ThemeTexture>().toEqualTypeOf<ReactThemeTexture>();
25
- expectTypeOf<ThemeAppearance>().toEqualTypeOf<ReactThemeAppearance>();
26
- });
@@ -1,83 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { canUseLocalHostBootstrap, getMetroHostFromScriptUrl, isConfiguredLocalApiUrl, isDeviceLocalUrl, resolveAppRuntimeUrl, resolveDeviceLocalUrl, shouldAutoReadClipboard } from "./runtime";
3
-
4
- vi.mock("react-native", () => ({
5
- NativeModules: { SourceCode: { scriptURL: null } },
6
- Platform: {
7
- OS: "ios",
8
- Version: "17.0",
9
- constants: { reactNativeVersion: { major: 0, minor: 83, patch: 2 } },
10
- },
11
- }));
12
-
13
- describe("mobile runtime helpers", () => {
14
- it("extracts the Metro host from a device script URL", () => {
15
- expect(getMetroHostFromScriptUrl("http://192.168.18.245:8081/index.bundle?platform=android")).toBe("192.168.18.245");
16
- expect(getMetroHostFromScriptUrl(null)).toBeNull();
17
- });
18
-
19
- it("rewrites localhost URLs to the Metro host for device builds", () => {
20
- const scriptUrl = "http://192.168.18.245:8081/index.bundle?platform=android";
21
-
22
- expect(resolveDeviceLocalUrl("http://localhost:8080", scriptUrl)).toBe("http://192.168.18.245:8080");
23
- expect(resolveDeviceLocalUrl("ws://127.0.0.1:8080/ws", scriptUrl)).toBe("ws://192.168.18.245:8080/ws");
24
- expect(resolveDeviceLocalUrl("https://chalk-api.q9labs.ai", scriptUrl)).toBe("https://chalk-api.q9labs.ai");
25
- });
26
-
27
- it("falls back to production URLs when release builds still carry localhost env values", () => {
28
- expect(resolveDeviceLocalUrl("http://localhost:8080", null, "https://chalk-api.q9labs.ai")).toBe("https://chalk-api.q9labs.ai");
29
- expect(resolveDeviceLocalUrl("ws://127.0.0.1:8080/ws", undefined, "wss://chalk-ws.q9labs.ai/ws")).toBe("wss://chalk-ws.q9labs.ai/ws");
30
- });
31
-
32
- it("preserves localhost URLs when the dev bundle is also served from localhost", () => {
33
- const scriptUrl = "http://localhost:8081/index.bundle?platform=ios";
34
-
35
- expect(resolveDeviceLocalUrl("http://localhost:8080", scriptUrl, "https://chalk-api.q9labs.ai")).toBe("http://localhost:8080");
36
- expect(resolveDeviceLocalUrl("ws://127.0.0.1:8080/ws", scriptUrl, "wss://chalk-ws.q9labs.ai/ws")).toBe("ws://127.0.0.1:8080/ws");
37
- });
38
-
39
- it("hard-forces production URLs when release builds disallow device-local hosts", () => {
40
- expect(
41
- resolveAppRuntimeUrl({
42
- configuredUrl: "http://localhost:8080",
43
- scriptUrl: "file:///android_asset/index.android.bundle",
44
- fallbackUrl: "https://chalk-api.q9labs.ai",
45
- allowDeviceLocal: false,
46
- }),
47
- ).toBe("https://chalk-api.q9labs.ai");
48
-
49
- expect(
50
- resolveAppRuntimeUrl({
51
- configuredUrl: "ws://localhost:8080/ws",
52
- scriptUrl: "file:///android_asset/index.android.bundle",
53
- fallbackUrl: "wss://chalk-ws.q9labs.ai/ws",
54
- allowDeviceLocal: false,
55
- }),
56
- ).toBe("wss://chalk-ws.q9labs.ai/ws");
57
- });
58
-
59
- it("detects local device URLs", () => {
60
- expect(isDeviceLocalUrl("http://localhost:8080")).toBe(true);
61
- expect(isDeviceLocalUrl("ws://127.0.0.1:8080/ws")).toBe(true);
62
- expect(isDeviceLocalUrl("https://chalk-api.q9labs.ai")).toBe(false);
63
- });
64
-
65
- it("detects configured local api urls even outside dev mode", () => {
66
- expect(isConfiguredLocalApiUrl("http://localhost:8080")).toBe(true);
67
- expect(isConfiguredLocalApiUrl(" ws://127.0.0.1:8080/ws ")).toBe(true);
68
- expect(isConfiguredLocalApiUrl("https://chalk-api.q9labs.ai")).toBe(false);
69
- expect(isConfiguredLocalApiUrl(undefined)).toBe(false);
70
- });
71
-
72
- it("only allows host bootstrap for resolved local runtime urls", () => {
73
- expect(canUseLocalHostBootstrap("http://localhost:8080", true)).toBe(true);
74
- expect(canUseLocalHostBootstrap("https://chalk-api.q9labs.ai", true)).toBe(false);
75
- expect(canUseLocalHostBootstrap("http://localhost:8080", false)).toBe(false);
76
- });
77
-
78
- it("skips eager clipboard reads on the iOS simulator", () => {
79
- expect(shouldAutoReadClipboard({ platform: "ios", isSimulator: true })).toBe(false);
80
- expect(shouldAutoReadClipboard({ platform: "ios", isSimulator: false })).toBe(true);
81
- expect(shouldAutoReadClipboard({ platform: "android", isSimulator: false })).toBe(true);
82
- });
83
- });