@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,137 +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 as { toURL: () => string } | 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
- muted: "#343536",
30
- mutedForeground: "#909192",
31
- error: "#dc2626",
32
- card: "#202122",
33
- surface: "#303132",
34
- raisedSurface: "#404142",
35
- insetSurface: "#505152",
36
- controlsBackground: "#606162",
37
- border: "#707172",
38
- ring: "#808182",
39
- darkCanvas: "#010203",
40
- },
41
- }));
42
-
43
- vi.mock("react-native", () => ({
44
- KeyboardAvoidingView: "KeyboardAvoidingView",
45
- Platform: { OS: "ios" },
46
- Pressable: "Pressable",
47
- StyleSheet: { absoluteFillObject: {}, create: <T,>(styles: T) => styles },
48
- Text: "Text",
49
- TextInput: "TextInput",
50
- View: "View",
51
- }));
52
- vi.mock("@cloudflare/react-native-webrtc", () => ({ RTCView: "RTCView" }));
53
- vi.mock("@hugeicons/react-native", () => ({ HugeiconsIcon: "HugeiconsIcon" }));
54
- vi.mock("./FaceAvatar", () => ({ FaceAvatar: "FaceAvatar" }));
55
- vi.mock("../ui/native-theme", () => ({ useNativeTheme: () => theme }));
56
- vi.mock("../utils/ios-simulator", () => ({ getIosSimulatorMediaMessage: () => "Media is unavailable in the simulator." }));
57
- vi.mock("./native-entrance/useEntranceController", () => ({ useEntranceController: () => controller }));
58
-
59
- import { EntranceViewIosPhone } from "./EntranceView.ios-phone";
60
-
61
- beforeEach(() => {
62
- controller.displayName = "Ada";
63
- controller.audioEnabled = true;
64
- controller.videoEnabled = true;
65
- controller.isSubmitting = false;
66
- controller.isInputFocused = false;
67
- controller.previewError = null;
68
- controller.previewStream = null;
69
- controller.simulatorMediaDisabled = false;
70
- controller.setDisplayName.mockReset();
71
- controller.setInputFocused.mockReset();
72
- controller.toggleAudio.mockReset();
73
- controller.toggleVideo.mockReset();
74
- controller.handleJoin.mockReset();
75
- });
76
-
77
- describe("EntranceViewIosPhone", () => {
78
- it("renders the phone preview sheet with the canonical Space title and join controls", () => {
79
- const onCancel = vi.fn();
80
- const { result } = renderHook(() => EntranceViewIosPhone({ onCancel, onJoin: vi.fn(), spaceName: "Design review" }));
81
- const tree = result.current;
82
- const screen = findElement(tree, "KeyboardAvoidingView");
83
- const cancel = findElement(tree, "Pressable", (element) => element.props.accessibilityLabel === "Cancel and leave Entrance");
84
- const textInput = findElement(tree, "TextInput");
85
- const join = findText(tree).find((text) => text === "Enter Space");
86
-
87
- expect(screen?.props.style).toEqual(expect.arrayContaining([expect.objectContaining({ backgroundColor: theme.colors.background })]));
88
- expect(findText(tree)).toContain("Design review");
89
- expect(cancel).toBeDefined();
90
- expect(textInput?.props.placeholder).toBe("Enter your name");
91
- expect(join).toBe("Enter Space");
92
-
93
- cancel?.props.onPress();
94
- expect(onCancel).toHaveBeenCalledOnce();
95
- });
96
-
97
- it("mounts the RTC preview only when the controller supplies an enabled stream", () => {
98
- controller.previewStream = { toURL: () => "stream://preview" };
99
-
100
- const { result } = renderHook(() => EntranceViewIosPhone({ onJoin: vi.fn(), spaceName: "Live Space" }));
101
- const preview = findElement(result.current, "RTCView");
102
-
103
- expect(preview?.props).toMatchObject({ mirror: true, objectFit: "cover", streamURL: "stream://preview" });
104
- });
105
- });
106
-
107
- interface NativeElementProps {
108
- readonly accessibilityLabel?: string;
109
- readonly children?: ReactNode;
110
- readonly onPress?: () => void;
111
- readonly placeholder?: string;
112
- readonly props?: unknown;
113
- readonly style?: unknown;
114
- }
115
-
116
- function findElement(node: ReactNode, type: string, matches: (element: ReactElement<NativeElementProps>) => boolean = () => true): ReactElement<NativeElementProps> | undefined {
117
- if (!isElement(node)) return undefined;
118
- if (node.type === type && matches(node)) return node;
119
-
120
- const children = node.props.children;
121
- for (const child of Array.isArray(children) ? children : [children]) {
122
- const found = findElement(child, type, matches);
123
- if (found) return found;
124
- }
125
- return undefined;
126
- }
127
-
128
- function findText(node: ReactNode): string[] {
129
- if (typeof node === "string") return [node];
130
- if (!isElement(node)) return [];
131
- const children = node.props.children;
132
- return (Array.isArray(children) ? children : [children]).flatMap(findText);
133
- }
134
-
135
- function isElement(node: ReactNode): node is ReactElement<NativeElementProps> {
136
- return typeof node === "object" && node !== null && "type" in node && "props" in node;
137
- }
@@ -1,15 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- vi.mock("../platform/platform", () => ({ resolvePlatformVariant: () => "android" }));
4
- vi.mock("./EntranceView.android", () => ({ EntranceViewAndroid: () => null }));
5
- vi.mock("./EntranceView.ios-pad", () => ({ EntranceViewIosPad: () => null }));
6
- vi.mock("./EntranceView.ios-phone", () => ({ EntranceViewIosPhone: () => null }));
7
- vi.mock("./EntranceView.macos", () => ({ EntranceViewMacos: () => null }));
8
-
9
- describe("EntranceView", () => {
10
- it("selects the platform-specific entrance", async () => {
11
- const { EntranceView } = await import("./EntranceView");
12
-
13
- expect(EntranceView({ spaceName: "Space", onJoin: vi.fn() })).toBeTruthy();
14
- });
15
- });
@@ -1,19 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { describe, expect, it } from "vitest";
3
-
4
- const source = readFileSync(new URL("./ReactionPicker.tsx", import.meta.url), "utf8");
5
-
6
- describe("ReactionPicker", () => {
7
- it("covers the persistent dock while keeping the reaction card inset", () => {
8
- expect(source).toContain("StyleSheet.absoluteFillObject");
9
- expect(source).not.toContain("paddingBottom: 110");
10
- expect(source).not.toContain("bottom: 94");
11
- });
12
-
13
- it("keeps close-button press feedback defined", () => {
14
- expect(source).toContain("pressed && styles.pressed");
15
- expect(source).toContain("pressed: {");
16
- expect(source).toContain("opacity: 0.72");
17
- expect(source).toContain("transform: [{ scale: 0.96 }]");
18
- });
19
- });
@@ -1,12 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- vi.mock("../media/native-webrtc", () => ({ RTCView: () => null }));
4
-
5
- describe("RtcVideoView", () => {
6
- it("reports the availability of the RTC view", async () => {
7
- const { hasRtcVideoView, RtcVideoView } = await import("./RtcVideoView");
8
-
9
- expect(hasRtcVideoView()).toBe(true);
10
- expect(RtcVideoView({ streamURL: "stream" })).toBeTruthy();
11
- });
12
- });
@@ -1,11 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- vi.mock("./SpaceView.shared", () => ({ SpaceViewShared: () => null }));
4
-
5
- describe("SpaceView.android", () => {
6
- it("exposes the canonical platform component name", async () => {
7
- const { SpaceViewAndroid } = await import("./SpaceView.android");
8
-
9
- expect(SpaceViewAndroid).toBeTypeOf("function");
10
- });
11
- });
@@ -1,11 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- vi.mock("./SpaceView.shared", () => ({ SpaceViewShared: () => null }));
4
-
5
- describe("SpaceView.ios-pad", () => {
6
- it("exposes the canonical platform component name", async () => {
7
- const { SpaceViewIosPad } = await import("./SpaceView.ios-pad");
8
-
9
- expect(SpaceViewIosPad).toBeTypeOf("function");
10
- });
11
- });
@@ -1,11 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- vi.mock("./SpaceView.shared", () => ({ SpaceViewShared: () => null }));
4
-
5
- describe("SpaceView.ios-phone", () => {
6
- it("exposes the canonical platform component name", async () => {
7
- const { SpaceViewIosPhone } = await import("./SpaceView.ios-phone");
8
-
9
- expect(SpaceViewIosPhone).toBeTypeOf("function");
10
- });
11
- });
@@ -1,11 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- vi.mock("./SpaceView.shared", () => ({ SpaceViewShared: () => null }));
4
-
5
- describe("SpaceView.macos", () => {
6
- it("exposes the canonical platform component name", async () => {
7
- const { SpaceViewMacos } = await import("./SpaceView.macos");
8
-
9
- expect(SpaceViewMacos).toBeTypeOf("function");
10
- });
11
- });
@@ -1,116 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
-
4
- const state = vi.hoisted(() => ({
5
- controller: {
6
- actionsOpen: false,
7
- activeReactions: [],
8
- canChat: true,
9
- canParticipants: true,
10
- derived: { allParticipants: [], gridPages: [], isCompactViewport: false, isStageMode: false, primaryContent: "grid", screenShareTrack: null, screenSharer: null },
11
- handRaised: false,
12
- handleLeave: vi.fn(),
13
- isCameraOff: false,
14
- isMuted: false,
15
- layout: { layout: "grid" },
16
- openPanel: vi.fn(),
17
- panel: null,
18
- raisedHandCount: 0,
19
- reactionPickerOpen: false,
20
- selfName: "Taylor",
21
- setActionsOpen: vi.fn(),
22
- setReactionPickerOpen: vi.fn(),
23
- simulatorMediaDisabled: false,
24
- spaceName: "Design space",
25
- chat: { unreadCount: 0 },
26
- whiteboard: { canDraw: true, elements: [], isOpen: false, openParticipants: [] },
27
- },
28
- useSpaceViewController: vi.fn(),
29
- }));
30
-
31
- vi.mock("react-native", () => ({
32
- Alert: { alert: vi.fn() },
33
- StyleSheet: { create: <T,>(styles: T) => styles },
34
- Text: "Text",
35
- View: "View",
36
- }));
37
- vi.mock("../ui/theme", () => ({ Theme: { colors: { darkCanvas: "#0a0a0b", primary: "#1bb6a6", primaryForeground: "#ffffff" } } }));
38
- vi.mock("../ui/native-theme", () => ({
39
- useNativeTheme: () => ({ colors: { darkCanvas: "#0a0a0b", primary: "#1bb6a6", primaryForeground: "#ffffff" } }),
40
- }));
41
- vi.mock("../utils/ios-simulator", () => ({ getIosSimulatorMediaMessage: () => "Media is unavailable in the simulator." }));
42
- vi.mock("./ReactionPicker", () => ({ ReactionPicker: "ReactionPicker" }));
43
- vi.mock("./native-space-view/SpaceActionMenu", () => ({ SpaceActionMenu: "SpaceActionMenu", selectSpaceReaction: vi.fn() }));
44
- vi.mock("./native-space-view/SettingsSheet", () => ({ SettingsSheet: "SettingsSheet" }));
45
- vi.mock("./native-space-view/SpacePanelSheet", () => ({ SpacePanelSheet: "SpacePanelSheet" }));
46
- vi.mock("./native-space-view/SpaceBottomDock.android", () => ({ SpaceBottomDockAndroid: "SpaceBottomDockAndroid" }));
47
- vi.mock("./native-space-view/SpaceGrid.android", () => ({ SpaceGridAndroid: "SpaceGridAndroid" }));
48
- vi.mock("./native-space-view/SpaceStage.android", () => ({ SpaceStageAndroid: "SpaceStageAndroid" }));
49
- vi.mock("./native-space-view/SpaceTopBar.android", () => ({ SpaceTopBarAndroid: "SpaceTopBarAndroid" }));
50
- vi.mock("./native-space-view/SpaceWhiteboardSurface", () => ({ SpaceWhiteboardSurface: "SpaceWhiteboardSurface" }));
51
- vi.mock("./native-space-view/useSpaceViewController", () => ({ useSpaceViewController: state.useSpaceViewController }));
52
-
53
- import { SpaceViewShared } from "./SpaceView.shared";
54
-
55
- describe("SpaceViewShared", () => {
56
- const source = readFileSync(new URL("./SpaceView.shared.tsx", import.meta.url), "utf8");
57
-
58
- beforeEach(() => {
59
- state.controller.whiteboard.isOpen = false;
60
- state.useSpaceViewController.mockClear();
61
- state.useSpaceViewController.mockReturnValue(state.controller);
62
- });
63
-
64
- afterEach(() => {
65
- state.controller.whiteboard.isOpen = false;
66
- state.useSpaceViewController.mockClear();
67
- });
68
-
69
- it("passes reconnecting state and capability-gated controls to the native surface", () => {
70
- const props = { onLeave: vi.fn(), onOpenDiagnostics: vi.fn(), reconnecting: true, logoUrl: "https://cdn.example.test/logo.png", spaceName: "Design space" };
71
- const tree = SpaceViewShared(props);
72
-
73
- expect(state.useSpaceViewController).toHaveBeenCalledWith(props);
74
- expect(findText(tree)).toContain("Reconnecting… controls will resume shortly.");
75
- expect(findElement(tree, "SpaceTopBarAndroid")).toMatchObject({ props: { logoUrl: props.logoUrl, spaceName: props.spaceName } });
76
- expect(findElement(tree, "SpaceBottomDockAndroid")).toMatchObject({ props: { onOpenChat: expect.any(Function), unreadChatCount: 0 } });
77
- expect(findElement(tree, "SpaceActionMenu")).toMatchObject({ props: { onOpenDiagnostics: props.onOpenDiagnostics } });
78
- });
79
-
80
- it("renders the whiteboard surface instead of a stage or grid when it is open", () => {
81
- state.controller.whiteboard.isOpen = true;
82
-
83
- const tree = SpaceViewShared({ onLeave: vi.fn() });
84
-
85
- expect(findElement(tree, "SpaceWhiteboardSurface")).toBeTruthy();
86
- expect(findElement(tree, "SpaceStageAndroid")).toBeUndefined();
87
- expect(findElement(tree, "SpaceGridAndroid")).toBeUndefined();
88
- });
89
-
90
- it("mounts the canonical panel sheet that owns the attachment composer", () => {
91
- const panelSource = readFileSync(new URL("./native-space-view/SpacePanelSheet.tsx", import.meta.url), "utf8");
92
- const chatSource = readFileSync(new URL("./native-space-view/SpaceChatSheet.tsx", import.meta.url), "utf8");
93
-
94
- expect(source).toContain("<SpacePanelSheet controller={controller} />");
95
- expect(source).not.toContain("<SpacePanel controller={controller} />");
96
- expect(source).toContain("<SettingsSheet controller={controller}");
97
- expect(panelSource).toContain("<SpaceChatSheet");
98
- expect(chatSource).toContain('accessibilityLabel="Attach file"');
99
- expect(chatSource).toContain("controller.removeChatAttachment(index)");
100
- });
101
- });
102
-
103
- function findElement(value: unknown, type: string): { readonly props?: Record<string, unknown> } | undefined {
104
- if (Array.isArray(value)) return value.map((child) => findElement(child, type)).find(Boolean);
105
- if (!value || typeof value !== "object") return undefined;
106
- const element = value as { readonly type?: unknown; readonly props?: Record<string, unknown> };
107
- if (element.type === type) return element;
108
- return findElement(element.props?.children, type);
109
- }
110
-
111
- function findText(value: unknown): string[] {
112
- if (typeof value === "string") return [value];
113
- if (Array.isArray(value)) return value.flatMap(findText);
114
- if (!value || typeof value !== "object") return [];
115
- return findText((value as { readonly props?: { readonly children?: unknown } }).props?.children);
116
- }
@@ -1,15 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
-
3
- vi.mock("../platform/platform", () => ({ resolvePlatformVariant: () => "android" }));
4
- vi.mock("./SpaceView.android", () => ({ SpaceViewAndroid: () => null }));
5
- vi.mock("./SpaceView.ios-pad", () => ({ SpaceViewIosPad: () => null }));
6
- vi.mock("./SpaceView.ios-phone", () => ({ SpaceViewIosPhone: () => null }));
7
- vi.mock("./SpaceView.macos", () => ({ SpaceViewMacos: () => null }));
8
-
9
- describe("SpaceView", () => {
10
- it("selects the platform-specific Space", async () => {
11
- const { SpaceView } = await import("./SpaceView");
12
-
13
- expect(SpaceView({ onLeave: vi.fn() })).toBeTruthy();
14
- });
15
- });
@@ -1,80 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import type { AndroidConnectionServiceControllerDependencies, AndroidConnectionServiceControllerInput } from "./android-connection-service-controller";
3
- import { AndroidConnectionServiceController } from "./android-connection-service-controller";
4
-
5
- async function flushMicrotasks(): Promise<void> {
6
- await Promise.resolve();
7
- await Promise.resolve();
8
- }
9
-
10
- function createInput(overrides: Partial<AndroidConnectionServiceControllerInput> = {}): AndroidConnectionServiceControllerInput {
11
- return {
12
- displayName: "Guest",
13
- enabled: true,
14
- hasVideo: true,
15
- joinNonce: 1,
16
- onDisconnectRequest: vi.fn(),
17
- phase: "joining",
18
- spaceId: "space-1",
19
- spaceName: "Daily Standup",
20
- ...overrides,
21
- };
22
- }
23
-
24
- function createDependencies() {
25
- let listener: Parameters<AndroidConnectionServiceControllerDependencies["addListener"]>[0] | undefined;
26
- const dependencies: AndroidConnectionServiceControllerDependencies = {
27
- addListener: vi.fn((nextListener) => {
28
- listener = nextListener;
29
- return vi.fn();
30
- }),
31
- endCall: vi.fn(async () => true),
32
- ensureRegistered: vi.fn(async () => true),
33
- setActive: vi.fn(async () => true),
34
- startCall: vi.fn(async () => true),
35
- };
36
- return { dependencies, getListener: () => listener };
37
- }
38
-
39
- describe("AndroidConnectionServiceController", () => {
40
- it("starts, activates, and filters disconnect events for the current call", async () => {
41
- const onDisconnectRequest = vi.fn();
42
- const { dependencies, getListener } = createDependencies();
43
- const controller = new AndroidConnectionServiceController(createInput({ onDisconnectRequest }), dependencies);
44
- const unsubscribe = controller.subscribe(() => {});
45
-
46
- expect(dependencies.ensureRegistered).toHaveBeenCalledTimes(1);
47
- expect(dependencies.startCall).toHaveBeenCalledWith({
48
- callId: "space-1:1",
49
- displayName: "Guest",
50
- hasVideo: true,
51
- spaceId: "space-1",
52
- spaceName: "Daily Standup",
53
- });
54
-
55
- controller.update(createInput({ onDisconnectRequest, phase: "live" }));
56
- await flushMicrotasks();
57
- expect(dependencies.setActive).toHaveBeenCalledWith("space-1:1");
58
-
59
- getListener()?.({ callId: "other:1", reason: "remote", type: "disconnect" });
60
- getListener()?.({ callId: "space-1:1", reason: "remote", type: "disconnect" });
61
- expect(onDisconnectRequest).toHaveBeenCalledTimes(1);
62
-
63
- controller.update(createInput({ onDisconnectRequest, phase: "entrance" }));
64
- await flushMicrotasks();
65
- expect(dependencies.endCall).toHaveBeenCalledWith("space-1:1", { reason: "canceled" });
66
-
67
- unsubscribe();
68
- });
69
-
70
- it("ends an active call locally and removes the native listener on unsubscribe", () => {
71
- const { dependencies } = createDependencies();
72
- const controller = new AndroidConnectionServiceController(createInput(), dependencies);
73
- const unsubscribe = controller.subscribe(() => {});
74
-
75
- unsubscribe();
76
-
77
- expect(dependencies.endCall).toHaveBeenCalledWith("space-1:1", { reason: "local" });
78
- expect(dependencies.addListener).toHaveBeenCalledTimes(1);
79
- });
80
- });
@@ -1,24 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { observeNativeFrameNotObservable } from "./episode-diagnostic-render-observer";
3
-
4
- const REGISTRY = Symbol.for("@chalk/private/episode-diagnostic-render-registry/v1");
5
-
6
- describe("observeNativeFrameNotObservable", () => {
7
- it("emits an explicit gap because the native RTC view exposes no first-frame callback", () => {
8
- const track = {};
9
- const report = vi.fn();
10
- (globalThis as unknown as Record<symbol, unknown>)[REGISTRY] = new WeakMap([[track, report]]);
11
-
12
- observeNativeFrameNotObservable(track);
13
-
14
- expect(report).toHaveBeenCalledWith("not_observable");
15
- });
16
-
17
- it("is inert when diagnostics did not register the track", () => {
18
- const report = vi.fn();
19
- (globalThis as unknown as Record<symbol, unknown>)[REGISTRY] = new WeakMap();
20
-
21
- expect(() => observeNativeFrameNotObservable({})).not.toThrow();
22
- expect(report).not.toHaveBeenCalled();
23
- });
24
- });
@@ -1,144 +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 { act, renderHook } from "../../test-support/test-renderer";
6
-
7
- const mocks = vi.hoisted(() => {
8
- const animationStops: ReturnType<typeof vi.fn>[] = [];
9
- const createAnimation = (config?: unknown) => {
10
- const stop = vi.fn();
11
- animationStops.push(stop);
12
- return { config, start: vi.fn(), stop };
13
- };
14
- class AnimatedValue {
15
- readonly initial: number;
16
- readonly setValue = vi.fn();
17
- readonly stopAnimation = vi.fn();
18
- constructor(initial: number) {
19
- this.initial = initial;
20
- }
21
- interpolate(input: unknown): { readonly input: unknown } {
22
- return { input };
23
- }
24
- }
25
-
26
- return {
27
- accessibilityInfo: {
28
- addEventListener: vi.fn(() => ({ remove: vi.fn() })),
29
- isReduceMotionEnabled: vi.fn(() => Promise.resolve(false)),
30
- },
31
- animationStops,
32
- animation: {
33
- delay: vi.fn((duration: number) => createAnimation({ duration })),
34
- loop: vi.fn((animation: unknown) => createAnimation({ animation })),
35
- parallel: vi.fn((animations: readonly unknown[]) => createAnimation({ animations })),
36
- sequence: vi.fn((animations: readonly unknown[]) => createAnimation({ animations })),
37
- timing: vi.fn((_value: unknown, config: unknown) => createAnimation(config)),
38
- Value: AnimatedValue,
39
- },
40
- };
41
- });
42
-
43
- vi.mock("react-native", () => ({ AccessibilityInfo: mocks.accessibilityInfo, Animated: { ...mocks.animation, createAnimatedComponent: (component: unknown) => component }, Pressable: "Pressable" }));
44
- vi.mock("react-native-svg", () => ({ Defs: "Defs", Ellipse: "Ellipse", G: "G", LinearGradient: "LinearGradient", Path: "Path", Stop: "Stop", Text: "SvgText", default: "Svg" }));
45
-
46
- import { Logo } from "./Logo";
47
-
48
- describe("Logo", () => {
49
- beforeEach(() => {
50
- vi.clearAllMocks();
51
- mocks.animationStops.length = 0;
52
- mocks.accessibilityInfo.isReduceMotionEnabled.mockResolvedValue(false);
53
- });
54
-
55
- it("uses the canonical mark geometry and keeps the wordmark static", () => {
56
- const { result } = renderHook(() => Logo({ accessibilityLabel: "Brand", color: "#123456", height: 96 }));
57
- const pressable = result.current;
58
- const svg = findElement(pressable, "Svg");
59
-
60
- expect(svg).toMatchObject({ props: { accessibilityLabel: "Brand", accessible: true, color: "#123456", height: 96, viewBox: "0 0 200 80", width: 240 } });
61
- expect(findElements(svg, "Path")).toHaveLength(4);
62
- expect(findElements(svg, "Ellipse")).toHaveLength(4);
63
- expect(findElements(svg, "SvgText")).toMatchObject([{ props: { fill: "currentColor", children: "chalk" } }]);
64
- expect(findElements(svg, "G")).toHaveLength(5);
65
- expect(findElements(svg, "SvgText")[0].props).not.toHaveProperty("rotation");
66
- });
67
-
68
- it("renders the mark fallback without adding an announced button", () => {
69
- const { result } = renderHook(() => Logo({ accessibilityLabel: null, height: 24, motion: "none", variant: "mark" }));
70
- const pressable = result.current;
71
- const svg = findElement(pressable, "Svg");
72
-
73
- expect(pressable.props).toMatchObject({ accessible: false });
74
- expect(svg).toMatchObject({ props: { accessible: false, height: 24, viewBox: "0 0 68 80", width: 20.4 } });
75
- expect(findElements(svg, "SvgText")).toHaveLength(0);
76
- });
77
-
78
- it("uses orbit by default and switches only the sticks to burst while active", async () => {
79
- const { result } = renderHook(() => Logo({}));
80
- await act(async () => await Promise.resolve());
81
-
82
- const initialTimingCount = mocks.animation.timing.mock.calls.length;
83
- act(() => {
84
- result.current.props.onHoverIn();
85
- });
86
-
87
- expect(mocks.animation.timing.mock.calls.length).toBeGreaterThan(initialTimingCount);
88
- expect(mocks.animation.timing.mock.calls.slice(initialTimingCount).map(([, config]) => (config as { readonly toValue: number }).toValue)).toEqual(expect.arrayContaining([-14, -9, -4, -14, 14, -6, 11, 12, 1.14, 1.16, 1.13]));
89
- expect(findElements(result.current, "SvgText")[0].props).not.toHaveProperty("rotation");
90
-
91
- act(() => {
92
- result.current.props.onHoverOut();
93
- });
94
- expect(mocks.animation.timing.mock.calls.length).toBeGreaterThan(initialTimingCount + 12);
95
- });
96
-
97
- it("does not start animations when reduced motion is enabled and removes the listener", async () => {
98
- mocks.accessibilityInfo.isReduceMotionEnabled.mockResolvedValue(true);
99
- const { result, unmount } = renderHook(() => Logo({}));
100
- await act(async () => await Promise.resolve());
101
-
102
- expect(mocks.animation.timing).not.toHaveBeenCalled();
103
- unmount();
104
- expect(mocks.accessibilityInfo.addEventListener.mock.results[0]?.value.remove).toHaveBeenCalledTimes(1);
105
- expect(findElements(result.current, "Svg")).toHaveLength(1);
106
- });
107
-
108
- it("stops the active orbit when the logo unmounts", async () => {
109
- const { unmount } = renderHook(() => Logo({}));
110
- await act(async () => await Promise.resolve());
111
-
112
- unmount();
113
-
114
- expect(mocks.animationStops.some((stop) => stop.mock.calls.length > 0)).toBe(true);
115
- });
116
- });
117
-
118
- function findElement(node: ReactNode, type: string): ReactElement<{ readonly children?: ReactNode; readonly [key: string]: unknown }> {
119
- if (isElement(node) && node.type === type) return node;
120
- if (!isElement(node)) throw new Error(`Expected ${type}`);
121
- const children = Array.isArray(node.props.children) ? node.props.children : [node.props.children];
122
- for (const child of children) {
123
- if (isElement(child) && child.type === type) return child;
124
- if (isElement(child)) {
125
- try {
126
- return findElement(child, type);
127
- } catch {
128
- continue;
129
- }
130
- }
131
- }
132
- throw new Error(`Expected ${type}`);
133
- }
134
-
135
- function findElements(node: ReactNode, type: string): ReactElement<{ readonly children?: ReactNode; readonly [key: string]: unknown }>[] {
136
- if (!isElement(node)) return [];
137
- const matches = node.type === type ? [node] : [];
138
- const children = Array.isArray(node.props.children) ? node.props.children : [node.props.children];
139
- return matches.concat(children.flatMap((child) => findElements(child, type)));
140
- }
141
-
142
- function isElement(node: ReactNode): node is ReactElement<{ readonly children?: ReactNode; readonly [key: string]: unknown }> {
143
- return typeof node === "object" && node !== null && "type" in node && "props" in node;
144
- }
@@ -1,47 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { createAnimationRefController, type StoppableAnimation } from "./native-animation-controller";
3
-
4
- function createAnimation(): StoppableAnimation {
5
- return {
6
- start: vi.fn(),
7
- stop: vi.fn(),
8
- };
9
- }
10
-
11
- describe("createAnimationRefController", () => {
12
- it("starts animations once per attachment and stops them on detachment", () => {
13
- const firstAnimation = createAnimation();
14
- const secondAnimation = createAnimation();
15
- const ref = createAnimationRefController(() => [firstAnimation, secondAnimation]);
16
-
17
- ref({});
18
- ref({});
19
-
20
- expect(firstAnimation.start).toHaveBeenCalledOnce();
21
- expect(secondAnimation.start).toHaveBeenCalledOnce();
22
- expect(firstAnimation.stop).not.toHaveBeenCalled();
23
-
24
- ref(null);
25
- ref(null);
26
-
27
- expect(firstAnimation.stop).toHaveBeenCalledOnce();
28
- expect(secondAnimation.stop).toHaveBeenCalledOnce();
29
- });
30
-
31
- it("creates a fresh animation set when a node is attached again", () => {
32
- const firstAnimation = createAnimation();
33
- const secondAnimation = createAnimation();
34
- const createAnimations = vi.fn().mockReturnValueOnce([firstAnimation]).mockReturnValueOnce([secondAnimation]);
35
- const ref = createAnimationRefController(createAnimations);
36
-
37
- ref({});
38
- ref(null);
39
- ref({});
40
-
41
- expect(createAnimations).toHaveBeenCalledTimes(2);
42
- expect(firstAnimation.start).toHaveBeenCalledOnce();
43
- expect(firstAnimation.stop).toHaveBeenCalledOnce();
44
- expect(secondAnimation.start).toHaveBeenCalledOnce();
45
- expect(secondAnimation.stop).not.toHaveBeenCalled();
46
- });
47
- });