1import { describe, expect, it } from "vite-plus/test";
2import { deriveBottomControlInsets, type NativeLayoutMetrics } from "./reserved-regions";
3import { deriveLayout, deriveWorkspacePaneLayout } from "./layout";
4
5const innerDisplay: NativeLayoutMetrics = {
6 width: 960,
7 height: 560,
8 horizontalSizeClass: "regular",
9 verticalBarEdge: "right",
10 safeArea: { top: 0, bottom: 20, left: 8, right: 72 },
11 reservedRegions: [],
12};
13
14describe("bottom controls around reserved regions", () => {
15 it("respects asymmetric side controls without reserving the bottom twice", () => {
16 expect(deriveBottomControlInsets(innerDisplay)).toEqual({ left: 8, right: 72, bottom: 0 });
17 });
18
19 it("leaves ordinary non-native clients unchanged", () => {
20 expect(deriveBottomControlInsets(null)).toEqual({ left: 0, right: 0, bottom: 0 });
21 });
22
23 it("keeps the editor entirely on one side of a book fold", () => {
24 const insets = deriveBottomControlInsets({
25 ...innerDisplay,
26 reservedRegions: [{ kind: "division", x: 456, y: 0, width: 48, height: 560 }],
27 });
28 expect(insets).toEqual({ left: 8, right: 504, bottom: 0 });
29 expect(innerDisplay.width - insets.left - insets.right).toBeGreaterThan(320);
30 });
31
32 it("keeps controls below a horizontal fold in a hands-free pose", () => {
33 expect(
34 deriveBottomControlInsets({
35 ...innerDisplay,
36 reservedRegions: [{ kind: "division", x: 0, y: 240, width: 960, height: 40 }],
37 }),
38 ).toEqual({ left: 8, right: 72, bottom: 0 });
39 });
40
41 it("moves a control away from a bottom camera while leaving a top camera alone", () => {
42 expect(
43 deriveBottomControlInsets({
44 ...innerDisplay,
45 reservedRegions: [
46 { kind: "occlusion", x: 8, y: 0, width: 60, height: 60 },
47 { kind: "occlusion", x: 8, y: 510, width: 60, height: 50 },
48 ],
49 }),
50 ).toEqual({ left: 68, right: 72, bottom: 0 });
51 });
52
53 it("restores the full editor width when the device opens flat", () => {
54 const folded = {
55 ...innerDisplay,
56 reservedRegions: [{ kind: "division" as const, x: 456, y: 0, width: 48, height: 560 }],
57 };
58 expect(deriveBottomControlInsets(folded)).not.toEqual(deriveBottomControlInsets(innerDisplay));
59 expect(deriveBottomControlInsets(innerDisplay)).toEqual({ left: 8, right: 72, bottom: 0 });
60 });
61});
62
63describe("Duo workspace sizing", () => {
64 it("shows both columns on a short regular-width inner display", () => {
65 const layout = deriveLayout({ ...innerDisplay, nativeMetrics: innerDisplay });
66 expect(layout.usesSplitView).toBe(true);
67 expect(
68 deriveWorkspacePaneLayout({
69 layout,
70 viewportWidth: innerDisplay.width,
71 primarySidebarPreferredVisible: true,
72 auxiliaryPanePreferredVisible: false,
73 }).contentPaneWidth,
74 ).toBeGreaterThan(320);
75 });
76
77 it("keeps an ordinary phone compact when it rotates", () => {
78 expect(deriveLayout({ width: 852, height: 393 }).usesSplitView).toBe(false);
79 for (const [width, height] of [
80 [932, 430],
81 [650, 500],
82 ]) {
83 const metrics: NativeLayoutMetrics = {
84 ...innerDisplay,
85 width: width!,
86 height: height!,
87 verticalBarEdge: "none",
88 safeArea: { top: 0, bottom: 20, left: 59, right: 59 },
89 };
90 expect(deriveLayout({ ...metrics, nativeMetrics: metrics }).usesSplitView).toBe(false);
91 const cameraMetrics = {
92 ...metrics,
93 reservedRegions: [{ kind: "occlusion" as const, x: 0, y: 0, width: 60, height: 60 }],
94 };
95 expect(deriveLayout({ ...cameraMetrics, nativeMetrics: cameraMetrics }).usesSplitView).toBe(
96 false,
97 );
98 }
99 expect(
100 deriveLayout({
101 width: 960,
102 height: 560,
103 nativeMetrics: {
104 ...innerDisplay,
105 horizontalSizeClass: "compact",
106 },
107 }).usesSplitView,
108 ).toBe(false);
109 });
110
111 it("requires enough safe width for both panes in a multitasking window", () => {
112 const metrics = { ...innerDisplay, width: 660 };
113 expect(deriveLayout({ ...metrics, nativeMetrics: metrics }).usesSplitView).toBe(false);
114 });
115
116 it("aligns the panes to the book fold and accounts for the reserved gap", () => {
117 const metrics = {
118 ...innerDisplay,
119 reservedRegions: [{ kind: "division" as const, x: 456, y: 0, width: 48, height: 560 }],
120 };
121 const layout = deriveLayout({ ...metrics, nativeMetrics: metrics });
122 expect(layout.listPaneWidth).toBe(456);
123 expect(layout.listPaneGap).toBe(48);
124 const panes = deriveWorkspacePaneLayout({
125 layout,
126 viewportWidth: 960,
127 primarySidebarPreferredVisible: true,
128 auxiliaryPanePreferredVisible: false,
129 });
130 expect(panes.contentPaneWidth).toBe(456);
131 expect(
132 deriveWorkspacePaneLayout({
133 layout,
134 viewportWidth: 960,
135 primarySidebarPreferredVisible: false,
136 auxiliaryPanePreferredVisible: false,
137 }).contentPaneWidth,
138 ).toBe(960);
139 });
140});