Skip to content
Source
Sign inSign up

home-route-visibility.test.tsx

137 lines4.4 KB

1import { RegistryContext } from "@effect/atom-react";
2import { Atom, AtomRegistry } from "effect/reactivity";
3import { act, useLayoutEffect } from "react";
4import { createRoot } from "react-dom/client";
5import { expect, it, vi } from "vite-plus/test";
6​
7import {
8 NativePrimaryColumnContext,
9 NativeWorkspaceTabActivityContext,
10} from "../../native/v5-workspace-context";
11​
12// The Thread in the detail column is the focused route on iPad split view.
13vi.mock("@react-navigation/native", () => ({
14 useNavigation: () => ({ isFocused: () => false }),
15}));
16​
17import {
18 recordHomeFocus,
19 recordHomeTransitionEnd,
20 recordHomeTransitionStart,
21 useAtomValueWhileVisible,
22 useHomeRouteVisible,
23} from "./home-route-visibility";
24​
25const shellsAtom = Atom.make(0).pipe(Atom.keepAlive);
26​
27function setup() {
28 (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
29 const registry = AtomRegistry.make();
30 let latest: number | undefined;
31 function Probe() {
32 const visible = useHomeRouteVisible();
33 const value = useAtomValueWhileVisible(shellsAtom, visible);
34 useLayoutEffect(() => {
35 latest = value;
36 });
37 return null;
38 }
39 const root = createRoot(document.createElement("div"));
40 const sidebar = {
41 presented: true,
42 selectedThreadKey: "env-1:thread-1",
43 selectedPullRequestKey: null,
44 };
45 return {
46 registry,
47 get latest() {
48 return latest;
49 },
50 showHome() {
51 act(() =>
52 root.render(
53 <RegistryContext.Provider value={registry}>
54 <NativePrimaryColumnContext value={sidebar}>
55 <Probe />
56 </NativePrimaryColumnContext>
57 </RegistryContext.Provider>,
58 ),
59 );
60 },
61 /** A phone has no primary column: Home is the root page of its tab's slot. */
62 showHomeInTabSlot(active: boolean) {
63 act(() =>
64 root.render(
65 <RegistryContext.Provider value={registry}>
66 <NativeWorkspaceTabActivityContext value={{ active, isActive: () => active }}>
67 <Probe />
68 </NativeWorkspaceTabActivityContext>
69 </RegistryContext.Provider>,
70 ),
71 );
72 },
73 [Symbol.dispose]() {
74 act(() => root.unmount());
75 registry.dispose();
76 },
77 };
79​
80it("keeps the sidebar thread list live after switching tabs beside a focused Thread", () => {
81 using home = setup();
82 // The primary column appearing is the only navigation event Home gets on iPad.
83 recordHomeTransitionStart(false);
84 home.showHome();
85 act(() => home.registry.set(shellsAtom, 1));
86 expect(home.latest).toBe(1);
87​
88 // Chats → PRs → Chats: Chats' column will-appear, then the PRs column's did-disappear.
89 act(() => {
90 recordHomeTransitionEnd(true, false);
91 recordHomeTransitionStart(false);
92 recordHomeTransitionEnd(true, false);
93 });
94 act(() => home.registry.set(shellsAtom, 2));
95​
96 expect(home.latest, "new thread shells reach the sidebar list").toBe(2);
97});
98​
99// Each phone tab slot shows its own copy of the Home route. PRs keeps Home focused; Settings
100// is a route of its own.
101it.each([
102 { tab: "PRs", homeFocusedUnderTab: true },
103 { tab: "Settings", homeFocusedUnderTab: false },
104])(
105 "keeps the phone thread list live after a round trip through $tab",
106 ({ homeFocusedUnderTab }) => {
107 using home = setup();
108 recordHomeFocus();
109 home.showHomeInTabSlot(true);
110 act(() => home.registry.set(shellsAtom, 1));
111 expect(home.latest).toBe(1);
112​
113 // To the other tab: its slot will-appear, then the Chats slot's did-disappear.
114 act(() => {
115 recordHomeTransitionStart(false);
116 recordHomeTransitionEnd(true, homeFocusedUnderTab);
117 });
118 home.showHomeInTabSlot(false);
119 act(() => home.registry.set(shellsAtom, 2));
120 expect(home.latest, "the list under another tab holds its rows").toBe(1);
121​
122 // Back to Chats: its slot will-appear, then the other slot's did-disappear comes last.
123 act(() => {
124 recordHomeFocus();
125 recordHomeTransitionStart(false);
126 recordHomeTransitionEnd(true, true);
127 });
128 home.showHomeInTabSlot(true);
129 act(() => home.registry.set(shellsAtom, 3));
130 expect(home.latest, "a server rename reaches the list").toBe(3);
131​
132 // A pushed Thread takes the focus and still stops the list.
133 act(() => recordHomeTransitionEnd(true, false));
134 act(() => home.registry.set(shellsAtom, 4));
135 expect(home.latest, "the list under a Thread holds its rows").toBe(3);
136 },
137);