your personal website on atproto - mirror
blento.app
1import { describe, it, expect, vi } from 'vitest';
2import type { Item } from '$lib/types';
3
4// Mock CardDefinitionsByType — tests don't need real card definitions
5vi.mock('$lib/cards', () => ({
6 CardDefinitionsByType: {}
7}));
8
9import { mirrorItemSize, mirrorLayout, shouldMirror } from './mirror';
10
11function makeItem(overrides: Partial<Item> & { id: string }): Item {
12 return {
13 w: 2,
14 h: 2,
15 x: 0,
16 y: 0,
17 mobileW: 4,
18 mobileH: 4,
19 mobileX: 0,
20 mobileY: 0,
21 cardType: 'text',
22 cardData: {},
23 ...overrides
24 };
25}
26
27describe('shouldMirror', () => {
28 it('mirrors when editedOn is 0 (never edited) and no layoutMode', () => {
29 expect(shouldMirror(0, undefined, false)).toBe(true);
30 expect(shouldMirror(0, undefined, true)).toBe(true);
31 });
32
33 it('mirrors when only one layout edited and no layoutMode', () => {
34 expect(shouldMirror(1, undefined, false)).toBe(true);
35 expect(shouldMirror(2, undefined, true)).toBe(true);
36 });
37
38 it('does not mirror when both layouts edited and no layoutMode', () => {
39 expect(shouldMirror(3, undefined, false)).toBe(false);
40 expect(shouldMirror(3, undefined, true)).toBe(false);
41 });
42
43 it('desktop-leads: mirrors only when editing desktop', () => {
44 expect(shouldMirror(3, 'desktop-leads', false)).toBe(true);
45 expect(shouldMirror(3, 'desktop-leads', true)).toBe(false);
46 });
47
48 it('mobile-leads: mirrors only when editing mobile', () => {
49 expect(shouldMirror(3, 'mobile-leads', true)).toBe(true);
50 expect(shouldMirror(3, 'mobile-leads', false)).toBe(false);
51 });
52
53 it('independent: never mirrors', () => {
54 expect(shouldMirror(0, 'independent', false)).toBe(false);
55 expect(shouldMirror(0, 'independent', true)).toBe(false);
56 });
57});
58
59describe('mirrorItemSize', () => {
60 it('desktop → mobile: doubles dimensions', () => {
61 const item = makeItem({ id: 'a', w: 3, h: 2 });
62 mirrorItemSize(item, false);
63 expect(item.mobileW).toBe(6);
64 expect(item.mobileH).toBe(4);
65 });
66
67 it('desktop → mobile: caps width at COLUMNS (8)', () => {
68 const item = makeItem({ id: 'a', w: 6, h: 2 });
69 mirrorItemSize(item, false);
70 expect(item.mobileW).toBe(8);
71 expect(item.mobileH).toBe(4);
72 });
73
74 it('mobile → desktop: halves dimensions with snap-even', () => {
75 const item = makeItem({ id: 'a', mobileW: 6, mobileH: 4 });
76 mirrorItemSize(item, true);
77 // snapEven(6/2) = snapEven(3) = max(2, round(1.5)*2) = max(2, 4) = 4
78 expect(item.w).toBe(4);
79 expect(item.h).toBe(2);
80 });
81
82 it('mobile → desktop: exact halves', () => {
83 const item = makeItem({ id: 'a', mobileW: 4, mobileH: 4 });
84 mirrorItemSize(item, true);
85 // snapEven(4/2) = snapEven(2) = max(2, round(1)*2) = 2
86 expect(item.w).toBe(2);
87 expect(item.h).toBe(2);
88 });
89
90 it('mobile → desktop: minimum width is 2', () => {
91 const item = makeItem({ id: 'a', mobileW: 2, mobileH: 2 });
92 mirrorItemSize(item, true);
93 expect(item.w).toBe(2); // snapEven(1) = max(2, round(0.5)*2) = max(2, 1*2) = 2
94 expect(item.h).toBe(1); // round(2/2) = 1
95 });
96});
97
98describe('mirrorLayout desktop → mobile', () => {
99 it('two items that fit side-by-side on mobile stay side-by-side', () => {
100 // Two 2-wide items next to each other on desktop → 4-wide on mobile, should fit in 8 cols
101 const a = makeItem({ id: 'a', x: 0, y: 0, w: 2, h: 2 });
102 const b = makeItem({ id: 'b', x: 2, y: 0, w: 2, h: 2 });
103 const items = [a, b];
104
105 mirrorLayout(items, false);
106
107 // Both should be on the same row
108 expect(a.mobileY).toBe(b.mobileY);
109 // They should not overlap
110 expect(a.mobileX + a.mobileW).toBeLessThanOrEqual(b.mobileX);
111 });
112
113 it('preserves reading order', () => {
114 const a = makeItem({ id: 'a', x: 0, y: 0, w: 4, h: 2 });
115 const b = makeItem({ id: 'b', x: 4, y: 0, w: 4, h: 2 });
116 const c = makeItem({ id: 'c', x: 0, y: 2, w: 4, h: 2 });
117 const items = [a, b, c];
118
119 mirrorLayout(items, false);
120
121 // a and b are on the same desktop row but become full-width on mobile (8 cols each)
122 // So they must stack. a should come before b, and b before c.
123 expect(a.mobileY).toBeLessThan(b.mobileY);
124 expect(b.mobileY).toBeLessThan(c.mobileY);
125 });
126
127 it('does not leave gaps when items fit next to each other', () => {
128 // Three 2-wide items on one desktop row → 4-wide on mobile, two fit per row
129 const a = makeItem({ id: 'a', x: 0, y: 0, w: 2, h: 2 });
130 const b = makeItem({ id: 'b', x: 2, y: 0, w: 2, h: 2 });
131 const c = makeItem({ id: 'c', x: 4, y: 0, w: 2, h: 2 });
132 const items = [a, b, c];
133
134 mirrorLayout(items, false);
135
136 // a and b should be on the same row
137 expect(a.mobileY).toBe(b.mobileY);
138 // c should be on the next row (only 4 cols left isn't enough... wait, 8 - 4 - 4 = 0)
139 // Actually a(4) + b(4) = 8, c must go to next row
140 expect(c.mobileY).toBe(a.mobileY + a.mobileH);
141 // c should start at x=0
142 expect(c.mobileX).toBe(0);
143 });
144
145 it('full-width items stack vertically', () => {
146 const a = makeItem({ id: 'a', x: 0, y: 0, w: 8, h: 2 });
147 const b = makeItem({ id: 'b', x: 0, y: 2, w: 8, h: 2 });
148 const items = [a, b];
149
150 mirrorLayout(items, false);
151
152 expect(a.mobileW).toBe(8);
153 expect(b.mobileW).toBe(8);
154 expect(a.mobileY).toBe(0);
155 expect(b.mobileY).toBe(a.mobileH);
156 });
157});
158
159describe('mirrorLayout mobile → desktop', () => {
160 it('two mobile items that fit side-by-side on desktop stay side-by-side', () => {
161 const a = makeItem({ id: 'a', mobileX: 0, mobileY: 0, mobileW: 4, mobileH: 4 });
162 const b = makeItem({ id: 'b', mobileX: 4, mobileY: 0, mobileW: 4, mobileH: 4 });
163 const items = [a, b];
164
165 mirrorLayout(items, true);
166
167 // Both should be on the same desktop row
168 expect(a.y).toBe(b.y);
169 expect(a.x + a.w).toBeLessThanOrEqual(b.x);
170 });
171
172 it('preserves reading order from mobile layout', () => {
173 const a = makeItem({ id: 'a', mobileX: 0, mobileY: 0, mobileW: 8, mobileH: 4 });
174 const b = makeItem({ id: 'b', mobileX: 0, mobileY: 4, mobileW: 8, mobileH: 4 });
175 const c = makeItem({ id: 'c', mobileX: 0, mobileY: 8, mobileW: 8, mobileH: 4 });
176 const items = [a, b, c];
177
178 mirrorLayout(items, true);
179
180 // a before b before c in desktop Y
181 expect(a.y).toBeLessThanOrEqual(b.y);
182 expect(b.y).toBeLessThanOrEqual(c.y);
183 });
184});