@praxisjs/composables 1.2.0 → 1.2.2
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.
- package/CHANGELOG.md +25 -0
- package/dist/browser.d.ts +41 -18
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +114 -112
- package/dist/browser.js.map +1 -1
- package/dist/dom.d.ts +24 -16
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +75 -78
- package/dist/dom.js.map +1 -1
- package/dist/list.d.ts +4 -1
- package/dist/list.d.ts.map +1 -1
- package/dist/list.js +29 -11
- package/dist/list.js.map +1 -1
- package/dist/upload.d.ts.map +1 -1
- package/dist/upload.js +32 -12
- package/dist/upload.js.map +1 -1
- package/dist/utilities.d.ts +13 -1
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +88 -26
- package/dist/utilities.js.map +1 -1
- package/package.json +14 -5
- package/src/browser.ts +129 -113
- package/src/dom.ts +77 -79
- package/src/list.ts +29 -10
- package/src/upload.ts +30 -11
- package/src/utilities.ts +101 -24
- package/dist/__tests__/browser.test.d.ts +0 -2
- package/dist/__tests__/browser.test.d.ts.map +0 -1
- package/dist/__tests__/browser.test.js +0 -333
- package/dist/__tests__/browser.test.js.map +0 -1
- package/dist/__tests__/dom.test.d.ts +0 -2
- package/dist/__tests__/dom.test.d.ts.map +0 -1
- package/dist/__tests__/dom.test.js +0 -416
- package/dist/__tests__/dom.test.js.map +0 -1
- package/dist/__tests__/list.test.d.ts +0 -2
- package/dist/__tests__/list.test.d.ts.map +0 -1
- package/dist/__tests__/list.test.js +0 -156
- package/dist/__tests__/list.test.js.map +0 -1
- package/dist/__tests__/upload.test.d.ts +0 -2
- package/dist/__tests__/upload.test.d.ts.map +0 -1
- package/dist/__tests__/upload.test.js +0 -531
- package/dist/__tests__/upload.test.js.map +0 -1
- package/dist/__tests__/utilities.test.d.ts +0 -2
- package/dist/__tests__/utilities.test.d.ts.map +0 -1
- package/dist/__tests__/utilities.test.js +0 -251
- package/dist/__tests__/utilities.test.js.map +0 -1
- package/src/__tests__/browser.test.ts +0 -395
- package/src/__tests__/dom.test.ts +0 -515
- package/src/__tests__/list.test.ts +0 -210
- package/src/__tests__/upload.test.ts +0 -639
- package/src/__tests__/utilities.test.ts +0 -327
- package/tsconfig.json +0 -8
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
3
|
-
import { signal } from "@praxisjs/core/internal";
|
|
4
|
-
import { Clipboard, Geolocation, TimeAgo, Pagination } from "../utilities";
|
|
5
|
-
// ── Clipboard ─────────────────────────────────────────────────────────────────
|
|
6
|
-
describe("Clipboard", () => {
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
Object.defineProperty(navigator, "clipboard", {
|
|
9
|
-
value: { writeText: vi.fn().mockResolvedValue(undefined) },
|
|
10
|
-
configurable: true,
|
|
11
|
-
});
|
|
12
|
-
});
|
|
13
|
-
it("starts with copied=false and empty content", () => {
|
|
14
|
-
const cb = new Clipboard();
|
|
15
|
-
const { copied, content } = cb.setup();
|
|
16
|
-
expect(copied()).toBe(false);
|
|
17
|
-
expect(content()).toBe("");
|
|
18
|
-
});
|
|
19
|
-
it("sets copied=true and content after copy", async () => {
|
|
20
|
-
const cb = new Clipboard();
|
|
21
|
-
const { copy, copied, content } = cb.setup();
|
|
22
|
-
await copy("hello");
|
|
23
|
-
expect(copied()).toBe(true);
|
|
24
|
-
expect(content()).toBe("hello");
|
|
25
|
-
});
|
|
26
|
-
it("resets copied after resetDelay", async () => {
|
|
27
|
-
vi.useFakeTimers();
|
|
28
|
-
const cb = new Clipboard(1000);
|
|
29
|
-
const { copy, copied } = cb.setup();
|
|
30
|
-
await copy("test");
|
|
31
|
-
expect(copied()).toBe(true);
|
|
32
|
-
vi.advanceTimersByTime(1000);
|
|
33
|
-
expect(copied()).toBe(false);
|
|
34
|
-
vi.useRealTimers();
|
|
35
|
-
});
|
|
36
|
-
it("warns on clipboard failure", async () => {
|
|
37
|
-
navigator.clipboard.writeText.mockRejectedValue(new Error("denied"));
|
|
38
|
-
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
39
|
-
const cb = new Clipboard();
|
|
40
|
-
const { copy } = cb.setup();
|
|
41
|
-
await copy("fail");
|
|
42
|
-
expect(warn).toHaveBeenCalled();
|
|
43
|
-
warn.mockRestore();
|
|
44
|
-
});
|
|
45
|
-
it("setTimeout is cleared on onUnmount", async () => {
|
|
46
|
-
vi.useFakeTimers();
|
|
47
|
-
const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
|
|
48
|
-
const cb = new Clipboard(1000);
|
|
49
|
-
const { copy } = cb.setup();
|
|
50
|
-
await copy("hello");
|
|
51
|
-
cb.onUnmount();
|
|
52
|
-
expect(clearTimeoutSpy).toHaveBeenCalled();
|
|
53
|
-
vi.useRealTimers();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
// ── Geolocation ───────────────────────────────────────────────────────────────
|
|
57
|
-
describe("Geolocation", () => {
|
|
58
|
-
function mockGeolocation(impl) {
|
|
59
|
-
vi.stubGlobal("navigator", {
|
|
60
|
-
...navigator,
|
|
61
|
-
geolocation: { getCurrentPosition: impl },
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
afterEach(() => { vi.unstubAllGlobals(); });
|
|
65
|
-
it("starts in loading state", () => {
|
|
66
|
-
mockGeolocation(() => { });
|
|
67
|
-
const geo = new Geolocation();
|
|
68
|
-
const { loading, lat, lng, error } = geo.setup();
|
|
69
|
-
expect(loading()).toBe(true);
|
|
70
|
-
expect(lat()).toBeNull();
|
|
71
|
-
expect(lng()).toBeNull();
|
|
72
|
-
expect(error()).toBeNull();
|
|
73
|
-
});
|
|
74
|
-
it("sets coordinates on success", () => {
|
|
75
|
-
mockGeolocation((success) => {
|
|
76
|
-
success({ coords: { latitude: 51.5, longitude: -0.1 } });
|
|
77
|
-
});
|
|
78
|
-
const geo = new Geolocation();
|
|
79
|
-
const { loading, lat, lng } = geo.setup();
|
|
80
|
-
expect(loading()).toBe(false);
|
|
81
|
-
expect(lat()).toBe(51.5);
|
|
82
|
-
expect(lng()).toBe(-0.1);
|
|
83
|
-
});
|
|
84
|
-
it("sets error on failure", () => {
|
|
85
|
-
const mockError = { code: 1, message: "denied" };
|
|
86
|
-
mockGeolocation((_s, error) => { error(mockError); });
|
|
87
|
-
const geo = new Geolocation();
|
|
88
|
-
const { loading, error } = geo.setup();
|
|
89
|
-
expect(loading()).toBe(false);
|
|
90
|
-
expect(error()).toBe(mockError);
|
|
91
|
-
});
|
|
92
|
-
it("success callback after unmount does not update state", () => {
|
|
93
|
-
let capturedSuccess;
|
|
94
|
-
mockGeolocation((success) => { capturedSuccess = success; });
|
|
95
|
-
const geo = new Geolocation();
|
|
96
|
-
const { lat, lng, loading } = geo.setup();
|
|
97
|
-
geo.onUnmount();
|
|
98
|
-
capturedSuccess({ coords: { latitude: 99, longitude: 99 } });
|
|
99
|
-
expect(lat()).toBeNull();
|
|
100
|
-
expect(lng()).toBeNull();
|
|
101
|
-
expect(loading()).toBe(true);
|
|
102
|
-
});
|
|
103
|
-
it("error callback after unmount does not update state", () => {
|
|
104
|
-
let capturedError;
|
|
105
|
-
mockGeolocation((_success, error) => { capturedError = error ?? undefined; });
|
|
106
|
-
const geo = new Geolocation();
|
|
107
|
-
const { error, loading } = geo.setup();
|
|
108
|
-
geo.onUnmount();
|
|
109
|
-
capturedError({ code: 1, message: "denied" });
|
|
110
|
-
expect(error()).toBeNull();
|
|
111
|
-
expect(loading()).toBe(true);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
// ── TimeAgo ───────────────────────────────────────────────────────────────────
|
|
115
|
-
describe("TimeAgo", () => {
|
|
116
|
-
beforeEach(() => { vi.useFakeTimers(); });
|
|
117
|
-
afterEach(() => { vi.useRealTimers(); });
|
|
118
|
-
const now = Date.now();
|
|
119
|
-
it("formats seconds ago", () => {
|
|
120
|
-
const ta = new TimeAgo(() => now - 30_000, "en");
|
|
121
|
-
const { value } = ta.setup();
|
|
122
|
-
expect(value()).toContain("30 seconds ago");
|
|
123
|
-
});
|
|
124
|
-
it("formats minutes ago", () => {
|
|
125
|
-
const ta = new TimeAgo(() => now - 5 * 60_000, "en");
|
|
126
|
-
const { value } = ta.setup();
|
|
127
|
-
expect(value()).toContain("5 minutes ago");
|
|
128
|
-
});
|
|
129
|
-
it("formats hours ago", () => {
|
|
130
|
-
const ta = new TimeAgo(() => now - 2 * 3_600_000, "en");
|
|
131
|
-
const { value } = ta.setup();
|
|
132
|
-
expect(value()).toContain("2 hours ago");
|
|
133
|
-
});
|
|
134
|
-
it("formats days ago", () => {
|
|
135
|
-
const ta = new TimeAgo(() => now - 3 * 86_400_000, "en");
|
|
136
|
-
const { value } = ta.setup();
|
|
137
|
-
expect(value()).toContain("3 days ago");
|
|
138
|
-
});
|
|
139
|
-
it("formats months ago", () => {
|
|
140
|
-
const ta = new TimeAgo(() => now - 60 * 86_400_000, "en");
|
|
141
|
-
const { value } = ta.setup();
|
|
142
|
-
expect(value()).toContain("2 months ago");
|
|
143
|
-
});
|
|
144
|
-
it("accepts a signal as source", () => {
|
|
145
|
-
const src = signal(now - 30_000);
|
|
146
|
-
const ta = new TimeAgo(src, "en");
|
|
147
|
-
const { value } = ta.setup();
|
|
148
|
-
expect(value()).toContain("30 seconds ago");
|
|
149
|
-
});
|
|
150
|
-
it("clears interval on unmount", () => {
|
|
151
|
-
const clearInterval = vi.spyOn(globalThis, "clearInterval");
|
|
152
|
-
const ta = new TimeAgo(() => now, "en");
|
|
153
|
-
ta.setup();
|
|
154
|
-
ta.onUnmount();
|
|
155
|
-
expect(clearInterval).toHaveBeenCalled();
|
|
156
|
-
});
|
|
157
|
-
it("interval callback fires after 60s — tick updates and value recomputes", () => {
|
|
158
|
-
const ta = new TimeAgo(() => now - 30_000, "en");
|
|
159
|
-
const { value } = ta.setup();
|
|
160
|
-
const before = value();
|
|
161
|
-
vi.advanceTimersByTime(60_000);
|
|
162
|
-
// After 60s the tick signal updates; value() still produces a valid string
|
|
163
|
-
expect(typeof value()).toBe("string");
|
|
164
|
-
expect(value()).not.toBe(undefined);
|
|
165
|
-
void before;
|
|
166
|
-
});
|
|
167
|
-
it("future date produces a positive relative time string (e.g. 'in 30 seconds')", () => {
|
|
168
|
-
// A future timestamp produces a positive diff; Intl.RelativeTimeFormat formats it as "in X"
|
|
169
|
-
const ta = new TimeAgo(() => now + 30_000, "en");
|
|
170
|
-
const { value } = ta.setup();
|
|
171
|
-
expect(value()).toContain("in 30 seconds");
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
// ── Pagination ────────────────────────────────────────────────────────────────
|
|
175
|
-
describe("Pagination", () => {
|
|
176
|
-
it("initialises with defaults", () => {
|
|
177
|
-
const p = new Pagination({ total: 100, pageSize: 10 });
|
|
178
|
-
const s = p.setup();
|
|
179
|
-
expect(s.page()).toBe(1);
|
|
180
|
-
expect(s.totalPages()).toBe(10);
|
|
181
|
-
expect(s.offset()).toBe(0);
|
|
182
|
-
expect(s.hasNext()).toBe(true);
|
|
183
|
-
expect(s.hasPrev()).toBe(false);
|
|
184
|
-
expect(s.pageSize()).toBe(10);
|
|
185
|
-
});
|
|
186
|
-
it("respects initial page", () => {
|
|
187
|
-
const p = new Pagination({ total: 100, pageSize: 10, initial: 3 });
|
|
188
|
-
const { page, offset } = p.setup();
|
|
189
|
-
expect(page()).toBe(3);
|
|
190
|
-
expect(offset()).toBe(20);
|
|
191
|
-
});
|
|
192
|
-
it("next() advances page", () => {
|
|
193
|
-
const p = new Pagination({ total: 30, pageSize: 10 });
|
|
194
|
-
const { page, next } = p.setup();
|
|
195
|
-
next();
|
|
196
|
-
expect(page()).toBe(2);
|
|
197
|
-
});
|
|
198
|
-
it("prev() goes back a page", () => {
|
|
199
|
-
const p = new Pagination({ total: 30, pageSize: 10, initial: 3 });
|
|
200
|
-
const { page, prev } = p.setup();
|
|
201
|
-
prev();
|
|
202
|
-
expect(page()).toBe(2);
|
|
203
|
-
});
|
|
204
|
-
it("next() does not go past last page", () => {
|
|
205
|
-
const p = new Pagination({ total: 10, pageSize: 10 });
|
|
206
|
-
const { page, next } = p.setup();
|
|
207
|
-
next();
|
|
208
|
-
expect(page()).toBe(1);
|
|
209
|
-
});
|
|
210
|
-
it("prev() does not go before first page", () => {
|
|
211
|
-
const p = new Pagination({ total: 30, pageSize: 10 });
|
|
212
|
-
const { page, prev } = p.setup();
|
|
213
|
-
prev();
|
|
214
|
-
expect(page()).toBe(1);
|
|
215
|
-
});
|
|
216
|
-
it("goTo() jumps to a page", () => {
|
|
217
|
-
const p = new Pagination({ total: 50, pageSize: 10 });
|
|
218
|
-
const { page, goTo } = p.setup();
|
|
219
|
-
goTo(4);
|
|
220
|
-
expect(page()).toBe(4);
|
|
221
|
-
});
|
|
222
|
-
it("first() jumps to page 1", () => {
|
|
223
|
-
const p = new Pagination({ total: 50, pageSize: 10, initial: 5 });
|
|
224
|
-
const { page, first } = p.setup();
|
|
225
|
-
first();
|
|
226
|
-
expect(page()).toBe(1);
|
|
227
|
-
});
|
|
228
|
-
it("last() jumps to last page", () => {
|
|
229
|
-
const p = new Pagination({ total: 50, pageSize: 10 });
|
|
230
|
-
const { page, last } = p.setup();
|
|
231
|
-
last();
|
|
232
|
-
expect(page()).toBe(5);
|
|
233
|
-
});
|
|
234
|
-
it("pages() returns array of page numbers", () => {
|
|
235
|
-
const p = new Pagination({ total: 30, pageSize: 10 });
|
|
236
|
-
const { pages } = p.setup();
|
|
237
|
-
expect(pages()).toEqual([1, 2, 3]);
|
|
238
|
-
});
|
|
239
|
-
it("clamps totalPages for fractional divisions", () => {
|
|
240
|
-
const p = new Pagination({ total: 25, pageSize: 10 });
|
|
241
|
-
const { totalPages } = p.setup();
|
|
242
|
-
expect(totalPages()).toBe(3);
|
|
243
|
-
});
|
|
244
|
-
it("throws when pageSize is 0", () => {
|
|
245
|
-
expect(() => new Pagination({ total: 10, pageSize: 0 })).toThrow("pageSize must be greater than 0");
|
|
246
|
-
});
|
|
247
|
-
it("throws when pageSize is negative", () => {
|
|
248
|
-
expect(() => new Pagination({ total: 10, pageSize: -1 })).toThrow("pageSize must be greater than 0");
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
//# sourceMappingURL=utilities.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.test.js","sourceRoot":"","sources":["../../src/__tests__/utilities.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { MediaQuery, ColorScheme, Mouse, KeyCombo, Idle } from "../browser";
|
|
5
|
-
|
|
6
|
-
function mockMatchMedia(matches: boolean, onAdd?: (_: string, fn: (e: MediaQueryListEvent) => void) => void) {
|
|
7
|
-
const mql = {
|
|
8
|
-
matches,
|
|
9
|
-
addEventListener: onAdd ?? vi.fn(),
|
|
10
|
-
removeEventListener: vi.fn(),
|
|
11
|
-
} as unknown as MediaQueryList;
|
|
12
|
-
vi.stubGlobal("matchMedia", () => mql);
|
|
13
|
-
return mql;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// ── MediaQuery ────────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
describe("MediaQuery", () => {
|
|
19
|
-
beforeEach(() => { vi.restoreAllMocks(); });
|
|
20
|
-
|
|
21
|
-
it("reads initial match state", () => {
|
|
22
|
-
mockMatchMedia(true);
|
|
23
|
-
const mq = new MediaQuery("(min-width: 768px)");
|
|
24
|
-
const { matches } = mq.setup() as { matches: () => boolean };
|
|
25
|
-
expect(matches()).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("updates matches when change event fires", () => {
|
|
29
|
-
let changeHandler: ((e: MediaQueryListEvent) => void) | undefined;
|
|
30
|
-
mockMatchMedia(false, (_: string, fn) => { changeHandler = fn; });
|
|
31
|
-
|
|
32
|
-
const mq = new MediaQuery("(min-width: 768px)");
|
|
33
|
-
const { matches } = mq.setup() as { matches: () => boolean };
|
|
34
|
-
expect(matches()).toBe(false);
|
|
35
|
-
|
|
36
|
-
changeHandler!({ matches: true } as MediaQueryListEvent);
|
|
37
|
-
expect(matches()).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("removes event listener on unmount", () => {
|
|
41
|
-
const mql = mockMatchMedia(false);
|
|
42
|
-
const mq = new MediaQuery("(min-width: 768px)");
|
|
43
|
-
mq.setup();
|
|
44
|
-
mq.onUnmount();
|
|
45
|
-
expect((mql.removeEventListener as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith("change", expect.any(Function));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("onUnmount is safe to call without setup (covers false branch)", () => {
|
|
49
|
-
const mq = new MediaQuery("(min-width: 768px)");
|
|
50
|
-
expect(() => mq.onUnmount()).not.toThrow();
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// ── ColorScheme ───────────────────────────────────────────────────────────────
|
|
55
|
-
|
|
56
|
-
describe("ColorScheme", () => {
|
|
57
|
-
beforeEach(() => { vi.restoreAllMocks(); });
|
|
58
|
-
|
|
59
|
-
it("exposes isDark and isLight as computed inverses", () => {
|
|
60
|
-
let changeHandler: ((e: MediaQueryListEvent) => void) | undefined;
|
|
61
|
-
mockMatchMedia(false, (_: string, fn) => { changeHandler = fn; });
|
|
62
|
-
|
|
63
|
-
const cs = new ColorScheme();
|
|
64
|
-
const { isDark, isLight } = cs.setup() as { isDark: () => boolean; isLight: () => boolean };
|
|
65
|
-
|
|
66
|
-
expect(isDark()).toBe(false);
|
|
67
|
-
expect(isLight()).toBe(true);
|
|
68
|
-
|
|
69
|
-
changeHandler!({ matches: true } as MediaQueryListEvent);
|
|
70
|
-
expect(isDark()).toBe(true);
|
|
71
|
-
expect(isLight()).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("removes event listener on unmount", () => {
|
|
75
|
-
const mql = mockMatchMedia(false);
|
|
76
|
-
const cs = new ColorScheme();
|
|
77
|
-
cs.setup();
|
|
78
|
-
cs.onUnmount();
|
|
79
|
-
expect((mql.removeEventListener as ReturnType<typeof vi.fn>)).toHaveBeenCalledWith("change", expect.any(Function));
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("onUnmount is safe to call without setup (covers false branch)", () => {
|
|
83
|
-
const cs = new ColorScheme();
|
|
84
|
-
expect(() => cs.onUnmount()).not.toThrow();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// ── Mouse ─────────────────────────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
describe("Mouse", () => {
|
|
91
|
-
it("starts at (0, 0)", () => {
|
|
92
|
-
const mouse = new Mouse();
|
|
93
|
-
const { x, y } = mouse.setup() as { x: () => number; y: () => number };
|
|
94
|
-
expect(x()).toBe(0);
|
|
95
|
-
expect(y()).toBe(0);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("updates x and y on mousemove", () => {
|
|
99
|
-
const mouse = new Mouse();
|
|
100
|
-
const { x, y } = mouse.setup() as { x: () => number; y: () => number };
|
|
101
|
-
|
|
102
|
-
window.dispatchEvent(new MouseEvent("mousemove", { clientX: 120, clientY: 80 }));
|
|
103
|
-
expect(x()).toBe(120);
|
|
104
|
-
expect(y()).toBe(80);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("removes listener on unmount", () => {
|
|
108
|
-
const remove = vi.spyOn(window, "removeEventListener");
|
|
109
|
-
const mouse = new Mouse();
|
|
110
|
-
mouse.setup();
|
|
111
|
-
mouse.onUnmount();
|
|
112
|
-
expect(remove).toHaveBeenCalledWith("mousemove", expect.any(Function));
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// ── KeyCombo ──────────────────────────────────────────────────────────────────
|
|
117
|
-
|
|
118
|
-
describe("KeyCombo", () => {
|
|
119
|
-
it("starts as not pressed", () => {
|
|
120
|
-
const kc = new KeyCombo("ctrl+s");
|
|
121
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
122
|
-
expect(pressed()).toBe(false);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("sets pressed=true when matching combo is pressed", () => {
|
|
126
|
-
const kc = new KeyCombo("ctrl+s");
|
|
127
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
128
|
-
|
|
129
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, key: "s" }));
|
|
130
|
-
expect(pressed()).toBe(true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("resets pressed on keyup", () => {
|
|
134
|
-
const kc = new KeyCombo("ctrl+s");
|
|
135
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
136
|
-
|
|
137
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, key: "s" }));
|
|
138
|
-
window.dispatchEvent(new KeyboardEvent("keyup"));
|
|
139
|
-
expect(pressed()).toBe(false);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("does not set pressed for wrong key", () => {
|
|
143
|
-
const kc = new KeyCombo("ctrl+s");
|
|
144
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
145
|
-
|
|
146
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, key: "z" }));
|
|
147
|
-
expect(pressed()).toBe(false);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("removes listeners on unmount", () => {
|
|
151
|
-
const remove = vi.spyOn(window, "removeEventListener");
|
|
152
|
-
const kc = new KeyCombo("ctrl+s");
|
|
153
|
-
kc.setup();
|
|
154
|
-
kc.onUnmount();
|
|
155
|
-
expect(remove).toHaveBeenCalledWith("keydown", expect.any(Function));
|
|
156
|
-
expect(remove).toHaveBeenCalledWith("keyup", expect.any(Function));
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("matches combo without ctrl modifier (ctrl branch: `: true`)", () => {
|
|
160
|
-
const kc = new KeyCombo("s");
|
|
161
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
162
|
-
// No ctrlKey needed — parts doesn't include "ctrl" so ctrl = true always
|
|
163
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { key: "s" }));
|
|
164
|
-
expect(pressed()).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("matches combo with shift modifier (shift branch: `e.shiftKey`)", () => {
|
|
168
|
-
const kc = new KeyCombo("ctrl+shift+s");
|
|
169
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
170
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, shiftKey: true, key: "s" }));
|
|
171
|
-
expect(pressed()).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("does not match when shift is required but not pressed", () => {
|
|
175
|
-
const kc = new KeyCombo("ctrl+shift+s");
|
|
176
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
177
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, shiftKey: false, key: "s" }));
|
|
178
|
-
expect(pressed()).toBe(false);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("matches combo with alt modifier (alt branch: `e.altKey`)", () => {
|
|
182
|
-
const kc = new KeyCombo("ctrl+alt+s");
|
|
183
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
184
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, altKey: true, key: "s" }));
|
|
185
|
-
expect(pressed()).toBe(true);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// ── Idle ──────────────────────────────────────────────────────────────────────
|
|
190
|
-
|
|
191
|
-
describe("Idle", () => {
|
|
192
|
-
it("starts as not idle", () => {
|
|
193
|
-
vi.useFakeTimers();
|
|
194
|
-
const idle = new Idle(500);
|
|
195
|
-
const { idle: isIdle } = idle.setup() as { idle: () => boolean };
|
|
196
|
-
expect(isIdle()).toBe(false);
|
|
197
|
-
vi.useRealTimers();
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it("becomes idle after timeout", () => {
|
|
201
|
-
vi.useFakeTimers();
|
|
202
|
-
const composable = new Idle(500);
|
|
203
|
-
const { idle: isIdle } = composable.setup() as { idle: () => boolean };
|
|
204
|
-
vi.advanceTimersByTime(500);
|
|
205
|
-
expect(isIdle()).toBe(true);
|
|
206
|
-
vi.useRealTimers();
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it("resets idle timer on activity", () => {
|
|
210
|
-
vi.useFakeTimers();
|
|
211
|
-
const composable = new Idle(500);
|
|
212
|
-
const { idle: isIdle } = composable.setup() as { idle: () => boolean };
|
|
213
|
-
vi.advanceTimersByTime(400);
|
|
214
|
-
window.dispatchEvent(new MouseEvent("mousemove"));
|
|
215
|
-
vi.advanceTimersByTime(400);
|
|
216
|
-
expect(isIdle()).toBe(false);
|
|
217
|
-
vi.useRealTimers();
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it("removes listeners on unmount", () => {
|
|
221
|
-
const remove = vi.spyOn(window, "removeEventListener");
|
|
222
|
-
const composable = new Idle(500);
|
|
223
|
-
composable.setup();
|
|
224
|
-
composable.onUnmount();
|
|
225
|
-
expect(remove).toHaveBeenCalled();
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("fires on scroll event", () => {
|
|
229
|
-
vi.useFakeTimers();
|
|
230
|
-
const composable = new Idle(500);
|
|
231
|
-
const { idle: isIdle } = composable.setup() as { idle: () => boolean };
|
|
232
|
-
vi.advanceTimersByTime(500);
|
|
233
|
-
expect(isIdle()).toBe(true);
|
|
234
|
-
window.dispatchEvent(new Event("scroll"));
|
|
235
|
-
expect(isIdle()).toBe(false);
|
|
236
|
-
vi.useRealTimers();
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it("fires on click event", () => {
|
|
240
|
-
vi.useFakeTimers();
|
|
241
|
-
const composable = new Idle(500);
|
|
242
|
-
const { idle: isIdle } = composable.setup() as { idle: () => boolean };
|
|
243
|
-
vi.advanceTimersByTime(500);
|
|
244
|
-
expect(isIdle()).toBe(true);
|
|
245
|
-
window.dispatchEvent(new MouseEvent("click"));
|
|
246
|
-
expect(isIdle()).toBe(false);
|
|
247
|
-
vi.useRealTimers();
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it("fires on keydown event", () => {
|
|
251
|
-
vi.useFakeTimers();
|
|
252
|
-
const composable = new Idle(500);
|
|
253
|
-
const { idle: isIdle } = composable.setup() as { idle: () => boolean };
|
|
254
|
-
vi.advanceTimersByTime(500);
|
|
255
|
-
expect(isIdle()).toBe(true);
|
|
256
|
-
window.dispatchEvent(new KeyboardEvent("keydown"));
|
|
257
|
-
expect(isIdle()).toBe(false);
|
|
258
|
-
vi.useRealTimers();
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it("activity after becoming idle resets the timer", () => {
|
|
262
|
-
vi.useFakeTimers();
|
|
263
|
-
const composable = new Idle(500);
|
|
264
|
-
const { idle: isIdle } = composable.setup() as { idle: () => boolean };
|
|
265
|
-
vi.advanceTimersByTime(500);
|
|
266
|
-
expect(isIdle()).toBe(true);
|
|
267
|
-
window.dispatchEvent(new MouseEvent("mousemove"));
|
|
268
|
-
expect(isIdle()).toBe(false);
|
|
269
|
-
// Should become idle again after full timeout elapses
|
|
270
|
-
vi.advanceTimersByTime(500);
|
|
271
|
-
expect(isIdle()).toBe(true);
|
|
272
|
-
vi.useRealTimers();
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
// ── KeyCombo (additional) ─────────────────────────────────────────────────────
|
|
277
|
-
|
|
278
|
-
describe("KeyCombo (additional)", () => {
|
|
279
|
-
it("fires on meta+s keydown", () => {
|
|
280
|
-
const kc = new KeyCombo("meta+s");
|
|
281
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
282
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { metaKey: true, key: "s" }));
|
|
283
|
-
expect(pressed()).toBe(true);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it("modifier-only combo (ctrl+shift) — keyup resets pressed to false", () => {
|
|
287
|
-
const kc = new KeyCombo("ctrl+shift");
|
|
288
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
289
|
-
// Fire keyup to cover the _keyupHandler arrow function inside the !key branch
|
|
290
|
-
window.dispatchEvent(new KeyboardEvent("keyup"));
|
|
291
|
-
expect(pressed()).toBe(false);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it("modifier-only combo (ctrl+shift) never fires", () => {
|
|
295
|
-
const kc = new KeyCombo("ctrl+shift");
|
|
296
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
297
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, shiftKey: true, key: "Control" }));
|
|
298
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, shiftKey: true, key: "Shift" }));
|
|
299
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, shiftKey: true, key: "a" }));
|
|
300
|
-
expect(pressed()).toBe(false);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("whitespace in combo string (ctrl + s) is handled gracefully — fires on ctrl+s", () => {
|
|
304
|
-
// Parts are trimmed, so "ctrl + s" becomes ["ctrl", "s"] after trim
|
|
305
|
-
const kc = new KeyCombo("ctrl + s");
|
|
306
|
-
const { pressed } = kc.setup() as { pressed: () => boolean };
|
|
307
|
-
window.dispatchEvent(new KeyboardEvent("keydown", { ctrlKey: true, key: "s" }));
|
|
308
|
-
expect(pressed()).toBe(true);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("setup() called twice registers only one set of listeners", () => {
|
|
312
|
-
const add = vi.spyOn(window, "addEventListener");
|
|
313
|
-
const kc = new KeyCombo("ctrl+s");
|
|
314
|
-
kc.setup();
|
|
315
|
-
kc.setup();
|
|
316
|
-
const keydownCalls = add.mock.calls.filter((c) => c[0] === "keydown").length;
|
|
317
|
-
expect(keydownCalls).toBe(1);
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// ── MediaQuery (additional) ───────────────────────────────────────────────────
|
|
322
|
-
|
|
323
|
-
describe("MediaQuery (additional)", () => {
|
|
324
|
-
beforeEach(() => { vi.restoreAllMocks(); });
|
|
325
|
-
|
|
326
|
-
it("setup() called twice registers only one listener", () => {
|
|
327
|
-
const mql = mockMatchMedia(false);
|
|
328
|
-
const mq = new MediaQuery("(min-width: 768px)");
|
|
329
|
-
mq.setup();
|
|
330
|
-
mq.setup();
|
|
331
|
-
expect((mql.addEventListener as ReturnType<typeof vi.fn>)).toHaveBeenCalledTimes(1);
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// ── ColorScheme (additional) ──────────────────────────────────────────────────
|
|
336
|
-
|
|
337
|
-
describe("ColorScheme (additional)", () => {
|
|
338
|
-
beforeEach(() => { vi.restoreAllMocks(); });
|
|
339
|
-
|
|
340
|
-
it("setup() called twice registers only one listener", () => {
|
|
341
|
-
const mql = mockMatchMedia(false);
|
|
342
|
-
const cs = new ColorScheme();
|
|
343
|
-
cs.setup();
|
|
344
|
-
cs.setup();
|
|
345
|
-
expect((mql.addEventListener as ReturnType<typeof vi.fn>)).toHaveBeenCalledTimes(1);
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// ── Mouse (additional) ────────────────────────────────────────────────────────
|
|
350
|
-
|
|
351
|
-
describe("Mouse (additional)", () => {
|
|
352
|
-
it("setup() called twice returns the same view object", () => {
|
|
353
|
-
const mouse = new Mouse();
|
|
354
|
-
const view1 = mouse.setup();
|
|
355
|
-
const view2 = mouse.setup();
|
|
356
|
-
expect(view1).toBe(view2);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it("setup() called twice registers only one mousemove listener", () => {
|
|
360
|
-
const add = vi.spyOn(window, "addEventListener");
|
|
361
|
-
const mouse = new Mouse();
|
|
362
|
-
mouse.setup();
|
|
363
|
-
mouse.setup();
|
|
364
|
-
const moveCalls = add.mock.calls.filter((c) => c[0] === "mousemove").length;
|
|
365
|
-
expect(moveCalls).toBe(1);
|
|
366
|
-
vi.restoreAllMocks();
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// ── Idle (additional) ─────────────────────────────────────────────────────────
|
|
371
|
-
|
|
372
|
-
describe("Idle (additional)", () => {
|
|
373
|
-
it("setup() called twice returns the same view object", () => {
|
|
374
|
-
vi.useFakeTimers();
|
|
375
|
-
const idle = new Idle(500);
|
|
376
|
-
const view1 = idle.setup();
|
|
377
|
-
const view2 = idle.setup();
|
|
378
|
-
expect(view1).toBe(view2);
|
|
379
|
-
idle.onUnmount();
|
|
380
|
-
vi.useRealTimers();
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it("setup() called twice registers activity listeners only once", () => {
|
|
384
|
-
vi.useFakeTimers();
|
|
385
|
-
const add = vi.spyOn(window, "addEventListener");
|
|
386
|
-
const idle = new Idle(500);
|
|
387
|
-
idle.setup();
|
|
388
|
-
idle.setup();
|
|
389
|
-
const moveCalls = add.mock.calls.filter((c) => c[0] === "mousemove").length;
|
|
390
|
-
expect(moveCalls).toBe(1);
|
|
391
|
-
idle.onUnmount();
|
|
392
|
-
vi.restoreAllMocks();
|
|
393
|
-
vi.useRealTimers();
|
|
394
|
-
});
|
|
395
|
-
});
|