@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/browser.d.ts +41 -18
  3. package/dist/browser.d.ts.map +1 -1
  4. package/dist/browser.js +114 -112
  5. package/dist/browser.js.map +1 -1
  6. package/dist/dom.d.ts +24 -16
  7. package/dist/dom.d.ts.map +1 -1
  8. package/dist/dom.js +75 -78
  9. package/dist/dom.js.map +1 -1
  10. package/dist/list.d.ts +4 -1
  11. package/dist/list.d.ts.map +1 -1
  12. package/dist/list.js +29 -11
  13. package/dist/list.js.map +1 -1
  14. package/dist/upload.d.ts.map +1 -1
  15. package/dist/upload.js +32 -12
  16. package/dist/upload.js.map +1 -1
  17. package/dist/utilities.d.ts +13 -1
  18. package/dist/utilities.d.ts.map +1 -1
  19. package/dist/utilities.js +88 -26
  20. package/dist/utilities.js.map +1 -1
  21. package/package.json +14 -5
  22. package/src/browser.ts +129 -113
  23. package/src/dom.ts +77 -79
  24. package/src/list.ts +29 -10
  25. package/src/upload.ts +30 -11
  26. package/src/utilities.ts +101 -24
  27. package/dist/__tests__/browser.test.d.ts +0 -2
  28. package/dist/__tests__/browser.test.d.ts.map +0 -1
  29. package/dist/__tests__/browser.test.js +0 -333
  30. package/dist/__tests__/browser.test.js.map +0 -1
  31. package/dist/__tests__/dom.test.d.ts +0 -2
  32. package/dist/__tests__/dom.test.d.ts.map +0 -1
  33. package/dist/__tests__/dom.test.js +0 -416
  34. package/dist/__tests__/dom.test.js.map +0 -1
  35. package/dist/__tests__/list.test.d.ts +0 -2
  36. package/dist/__tests__/list.test.d.ts.map +0 -1
  37. package/dist/__tests__/list.test.js +0 -156
  38. package/dist/__tests__/list.test.js.map +0 -1
  39. package/dist/__tests__/upload.test.d.ts +0 -2
  40. package/dist/__tests__/upload.test.d.ts.map +0 -1
  41. package/dist/__tests__/upload.test.js +0 -531
  42. package/dist/__tests__/upload.test.js.map +0 -1
  43. package/dist/__tests__/utilities.test.d.ts +0 -2
  44. package/dist/__tests__/utilities.test.d.ts.map +0 -1
  45. package/dist/__tests__/utilities.test.js +0 -251
  46. package/dist/__tests__/utilities.test.js.map +0 -1
  47. package/src/__tests__/browser.test.ts +0 -395
  48. package/src/__tests__/dom.test.ts +0 -515
  49. package/src/__tests__/list.test.ts +0 -210
  50. package/src/__tests__/upload.test.ts +0 -639
  51. package/src/__tests__/utilities.test.ts +0 -327
  52. package/tsconfig.json +0 -8
@@ -1,327 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
- import { signal } from "@praxisjs/core/internal";
4
-
5
- import { Clipboard, Geolocation, TimeAgo, Pagination } from "../utilities";
6
-
7
- // ── Clipboard ─────────────────────────────────────────────────────────────────
8
-
9
- describe("Clipboard", () => {
10
- beforeEach(() => {
11
- Object.defineProperty(navigator, "clipboard", {
12
- value: { writeText: vi.fn().mockResolvedValue(undefined) },
13
- configurable: true,
14
- });
15
- });
16
-
17
- it("starts with copied=false and empty content", () => {
18
- const cb = new Clipboard();
19
- const { copied, content } = cb.setup() as { copied: () => boolean; content: () => string };
20
- expect(copied()).toBe(false);
21
- expect(content()).toBe("");
22
- });
23
-
24
- it("sets copied=true and content after copy", async () => {
25
- const cb = new Clipboard();
26
- const { copy, copied, content } = cb.setup() as {
27
- copy: (t: string) => Promise<void>;
28
- copied: () => boolean;
29
- content: () => string;
30
- };
31
- await copy("hello");
32
- expect(copied()).toBe(true);
33
- expect(content()).toBe("hello");
34
- });
35
-
36
- it("resets copied after resetDelay", async () => {
37
- vi.useFakeTimers();
38
- const cb = new Clipboard(1000);
39
- const { copy, copied } = cb.setup() as {
40
- copy: (t: string) => Promise<void>;
41
- copied: () => boolean;
42
- };
43
- await copy("test");
44
- expect(copied()).toBe(true);
45
- vi.advanceTimersByTime(1000);
46
- expect(copied()).toBe(false);
47
- vi.useRealTimers();
48
- });
49
-
50
- it("warns on clipboard failure", async () => {
51
- (navigator.clipboard.writeText as ReturnType<typeof vi.fn>).mockRejectedValue(new Error("denied"));
52
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
53
- const cb = new Clipboard();
54
- const { copy } = cb.setup() as { copy: (t: string) => Promise<void> };
55
- await copy("fail");
56
- expect(warn).toHaveBeenCalled();
57
- warn.mockRestore();
58
- });
59
-
60
- it("setTimeout is cleared on onUnmount", async () => {
61
- vi.useFakeTimers();
62
- const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout");
63
- const cb = new Clipboard(1000);
64
- const { copy } = cb.setup() as { copy: (t: string) => Promise<void> };
65
- await copy("hello");
66
- cb.onUnmount();
67
- expect(clearTimeoutSpy).toHaveBeenCalled();
68
- vi.useRealTimers();
69
- });
70
- });
71
-
72
- // ── Geolocation ───────────────────────────────────────────────────────────────
73
-
74
- describe("Geolocation", () => {
75
- function mockGeolocation(impl: (success: PositionCallback, error?: PositionErrorCallback | null) => void) {
76
- vi.stubGlobal("navigator", {
77
- ...navigator,
78
- geolocation: { getCurrentPosition: impl },
79
- });
80
- }
81
-
82
- afterEach(() => { vi.unstubAllGlobals(); });
83
-
84
- it("starts in loading state", () => {
85
- mockGeolocation(() => {});
86
- const geo = new Geolocation();
87
- const { loading, lat, lng, error } = geo.setup() as {
88
- loading: () => boolean;
89
- lat: () => number | null;
90
- lng: () => number | null;
91
- error: () => GeolocationPositionError | null;
92
- };
93
- expect(loading()).toBe(true);
94
- expect(lat()).toBeNull();
95
- expect(lng()).toBeNull();
96
- expect(error()).toBeNull();
97
- });
98
-
99
- it("sets coordinates on success", () => {
100
- mockGeolocation((success) => {
101
- success({ coords: { latitude: 51.5, longitude: -0.1 } } as GeolocationPosition);
102
- });
103
- const geo = new Geolocation();
104
- const { loading, lat, lng } = geo.setup() as {
105
- loading: () => boolean;
106
- lat: () => number | null;
107
- lng: () => number | null;
108
- };
109
- expect(loading()).toBe(false);
110
- expect(lat()).toBe(51.5);
111
- expect(lng()).toBe(-0.1);
112
- });
113
-
114
- it("sets error on failure", () => {
115
- const mockError = { code: 1, message: "denied" } as GeolocationPositionError;
116
- mockGeolocation((_s, error) => { error!(mockError); });
117
- const geo = new Geolocation();
118
- const { loading, error } = geo.setup() as {
119
- loading: () => boolean;
120
- error: () => GeolocationPositionError | null;
121
- };
122
- expect(loading()).toBe(false);
123
- expect(error()).toBe(mockError);
124
- });
125
-
126
- it("success callback after unmount does not update state", () => {
127
- let capturedSuccess: PositionCallback | undefined;
128
- mockGeolocation((success) => { capturedSuccess = success; });
129
-
130
- const geo = new Geolocation();
131
- const { lat, lng, loading } = geo.setup() as {
132
- lat: () => number | null;
133
- lng: () => number | null;
134
- loading: () => boolean;
135
- };
136
-
137
- geo.onUnmount();
138
- capturedSuccess!({ coords: { latitude: 99, longitude: 99 } } as GeolocationPosition);
139
-
140
- expect(lat()).toBeNull();
141
- expect(lng()).toBeNull();
142
- expect(loading()).toBe(true);
143
- });
144
-
145
- it("error callback after unmount does not update state", () => {
146
- let capturedError: PositionErrorCallback | undefined;
147
- mockGeolocation((_success, error) => { capturedError = error ?? undefined; });
148
-
149
- const geo = new Geolocation();
150
- const { error, loading } = geo.setup() as {
151
- error: () => GeolocationPositionError | null;
152
- loading: () => boolean;
153
- };
154
-
155
- geo.onUnmount();
156
- capturedError!({ code: 1, message: "denied" } as GeolocationPositionError);
157
-
158
- expect(error()).toBeNull();
159
- expect(loading()).toBe(true);
160
- });
161
- });
162
-
163
- // ── TimeAgo ───────────────────────────────────────────────────────────────────
164
-
165
- describe("TimeAgo", () => {
166
- beforeEach(() => { vi.useFakeTimers(); });
167
- afterEach(() => { vi.useRealTimers(); });
168
-
169
- const now = Date.now();
170
-
171
- it("formats seconds ago", () => {
172
- const ta = new TimeAgo(() => now - 30_000, "en");
173
- const { value } = ta.setup() as { value: () => string };
174
- expect(value()).toContain("30 seconds ago");
175
- });
176
-
177
- it("formats minutes ago", () => {
178
- const ta = new TimeAgo(() => now - 5 * 60_000, "en");
179
- const { value } = ta.setup() as { value: () => string };
180
- expect(value()).toContain("5 minutes ago");
181
- });
182
-
183
- it("formats hours ago", () => {
184
- const ta = new TimeAgo(() => now - 2 * 3_600_000, "en");
185
- const { value } = ta.setup() as { value: () => string };
186
- expect(value()).toContain("2 hours ago");
187
- });
188
-
189
- it("formats days ago", () => {
190
- const ta = new TimeAgo(() => now - 3 * 86_400_000, "en");
191
- const { value } = ta.setup() as { value: () => string };
192
- expect(value()).toContain("3 days ago");
193
- });
194
-
195
- it("formats months ago", () => {
196
- const ta = new TimeAgo(() => now - 60 * 86_400_000, "en");
197
- const { value } = ta.setup() as { value: () => string };
198
- expect(value()).toContain("2 months ago");
199
- });
200
-
201
- it("accepts a signal as source", () => {
202
- const src = signal(now - 30_000);
203
- const ta = new TimeAgo(src, "en");
204
- const { value } = ta.setup() as { value: () => string };
205
- expect(value()).toContain("30 seconds ago");
206
- });
207
-
208
- it("clears interval on unmount", () => {
209
- const clearInterval = vi.spyOn(globalThis, "clearInterval");
210
- const ta = new TimeAgo(() => now, "en");
211
- ta.setup();
212
- ta.onUnmount();
213
- expect(clearInterval).toHaveBeenCalled();
214
- });
215
-
216
- it("interval callback fires after 60s — tick updates and value recomputes", () => {
217
- const ta = new TimeAgo(() => now - 30_000, "en");
218
- const { value } = ta.setup() as { value: () => string };
219
- const before = value();
220
- vi.advanceTimersByTime(60_000);
221
- // After 60s the tick signal updates; value() still produces a valid string
222
- expect(typeof value()).toBe("string");
223
- expect(value()).not.toBe(undefined);
224
- void before;
225
- });
226
-
227
- it("future date produces a positive relative time string (e.g. 'in 30 seconds')", () => {
228
- // A future timestamp produces a positive diff; Intl.RelativeTimeFormat formats it as "in X"
229
- const ta = new TimeAgo(() => now + 30_000, "en");
230
- const { value } = ta.setup() as { value: () => string };
231
- expect(value()).toContain("in 30 seconds");
232
- });
233
- });
234
-
235
- // ── Pagination ────────────────────────────────────────────────────────────────
236
-
237
- describe("Pagination", () => {
238
- it("initialises with defaults", () => {
239
- const p = new Pagination({ total: 100, pageSize: 10 });
240
- const s = p.setup() as {
241
- page: () => number; totalPages: () => number; offset: () => number;
242
- hasNext: () => boolean; hasPrev: () => boolean; pageSize: () => number;
243
- };
244
- expect(s.page()).toBe(1);
245
- expect(s.totalPages()).toBe(10);
246
- expect(s.offset()).toBe(0);
247
- expect(s.hasNext()).toBe(true);
248
- expect(s.hasPrev()).toBe(false);
249
- expect(s.pageSize()).toBe(10);
250
- });
251
-
252
- it("respects initial page", () => {
253
- const p = new Pagination({ total: 100, pageSize: 10, initial: 3 });
254
- const { page, offset } = p.setup() as { page: () => number; offset: () => number };
255
- expect(page()).toBe(3);
256
- expect(offset()).toBe(20);
257
- });
258
-
259
- it("next() advances page", () => {
260
- const p = new Pagination({ total: 30, pageSize: 10 });
261
- const { page, next } = p.setup() as { page: () => number; next: () => void };
262
- (next as () => void)();
263
- expect(page()).toBe(2);
264
- });
265
-
266
- it("prev() goes back a page", () => {
267
- const p = new Pagination({ total: 30, pageSize: 10, initial: 3 });
268
- const { page, prev } = p.setup() as { page: () => number; prev: () => void };
269
- (prev as () => void)();
270
- expect(page()).toBe(2);
271
- });
272
-
273
- it("next() does not go past last page", () => {
274
- const p = new Pagination({ total: 10, pageSize: 10 });
275
- const { page, next } = p.setup() as { page: () => number; next: () => void };
276
- (next as () => void)();
277
- expect(page()).toBe(1);
278
- });
279
-
280
- it("prev() does not go before first page", () => {
281
- const p = new Pagination({ total: 30, pageSize: 10 });
282
- const { page, prev } = p.setup() as { page: () => number; prev: () => void };
283
- (prev as () => void)();
284
- expect(page()).toBe(1);
285
- });
286
-
287
- it("goTo() jumps to a page", () => {
288
- const p = new Pagination({ total: 50, pageSize: 10 });
289
- const { page, goTo } = p.setup() as { page: () => number; goTo: (n: number) => void };
290
- (goTo as (n: number) => void)(4);
291
- expect(page()).toBe(4);
292
- });
293
-
294
- it("first() jumps to page 1", () => {
295
- const p = new Pagination({ total: 50, pageSize: 10, initial: 5 });
296
- const { page, first } = p.setup() as { page: () => number; first: () => void };
297
- (first as () => void)();
298
- expect(page()).toBe(1);
299
- });
300
-
301
- it("last() jumps to last page", () => {
302
- const p = new Pagination({ total: 50, pageSize: 10 });
303
- const { page, last } = p.setup() as { page: () => number; last: () => void };
304
- (last as () => void)();
305
- expect(page()).toBe(5);
306
- });
307
-
308
- it("pages() returns array of page numbers", () => {
309
- const p = new Pagination({ total: 30, pageSize: 10 });
310
- const { pages } = p.setup() as { pages: () => number[] };
311
- expect((pages as () => number[])()).toEqual([1, 2, 3]);
312
- });
313
-
314
- it("clamps totalPages for fractional divisions", () => {
315
- const p = new Pagination({ total: 25, pageSize: 10 });
316
- const { totalPages } = p.setup() as { totalPages: () => number };
317
- expect(totalPages()).toBe(3);
318
- });
319
-
320
- it("throws when pageSize is 0", () => {
321
- expect(() => new Pagination({ total: 10, pageSize: 0 })).toThrow("pageSize must be greater than 0");
322
- });
323
-
324
- it("throws when pageSize is negative", () => {
325
- expect(() => new Pagination({ total: 10, pageSize: -1 })).toThrow("pageSize must be greater than 0");
326
- });
327
- });
package/tsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src"
6
- },
7
- "include": ["src"]
8
- }