@praxisjs/composables 0.1.2 → 0.1.4

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @praxisjs/composables
2
2
 
3
+ ## 0.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [fe39901]
8
+ - @praxisjs/core@0.4.1
9
+
10
+ ## 0.1.3
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [f52354d]
15
+ - @praxisjs/core@0.4.0
16
+
3
17
  ## 0.1.2
4
18
 
5
19
  ### Patch Changes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/browser.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,94 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { useMediaQuery, useColorScheme, useMouse, useKeyCombo, useIdle } from "../browser";
4
+ // ---------- useMediaQuery ----------
5
+ describe("useMediaQuery", () => {
6
+ it("returns computed that reflects matchMedia result", () => {
7
+ Object.defineProperty(window, "matchMedia", {
8
+ writable: true,
9
+ value: vi.fn((query) => ({
10
+ matches: query === "(prefers-color-scheme: dark)",
11
+ addEventListener: vi.fn(),
12
+ removeEventListener: vi.fn(),
13
+ })),
14
+ });
15
+ const isDark = useMediaQuery("(prefers-color-scheme: dark)");
16
+ expect(isDark()).toBe(true);
17
+ const isLight = useMediaQuery("(prefers-color-scheme: light)");
18
+ expect(isLight()).toBe(false);
19
+ });
20
+ });
21
+ // ---------- useColorScheme ----------
22
+ describe("useColorScheme", () => {
23
+ it("isDark and isLight are complementary", () => {
24
+ Object.defineProperty(window, "matchMedia", {
25
+ writable: true,
26
+ value: vi.fn(() => ({
27
+ matches: true,
28
+ addEventListener: vi.fn(),
29
+ removeEventListener: vi.fn(),
30
+ })),
31
+ });
32
+ const { isDark, isLight } = useColorScheme();
33
+ expect(isDark()).toBe(true);
34
+ expect(isLight()).toBe(false);
35
+ });
36
+ });
37
+ // ---------- useMouse ----------
38
+ describe("useMouse", () => {
39
+ it("starts at (0, 0)", () => {
40
+ const { x, y } = useMouse();
41
+ expect(x()).toBe(0);
42
+ expect(y()).toBe(0);
43
+ });
44
+ it("updates on mousemove event", () => {
45
+ const { x, y } = useMouse();
46
+ window.dispatchEvent(new MouseEvent("mousemove", { clientX: 100, clientY: 200 }));
47
+ expect(x()).toBe(100);
48
+ expect(y()).toBe(200);
49
+ });
50
+ });
51
+ // ---------- useKeyCombo ----------
52
+ describe("useKeyCombo", () => {
53
+ it("starts as false", () => {
54
+ const pressed = useKeyCombo("ctrl+s");
55
+ expect(pressed()).toBe(false);
56
+ });
57
+ it("becomes true on matching keydown", () => {
58
+ const pressed = useKeyCombo("ctrl+s");
59
+ window.dispatchEvent(new KeyboardEvent("keydown", { key: "s", ctrlKey: true }));
60
+ expect(pressed()).toBe(true);
61
+ });
62
+ it("resets to false on keyup", () => {
63
+ const pressed = useKeyCombo("ctrl+s");
64
+ window.dispatchEvent(new KeyboardEvent("keydown", { key: "s", ctrlKey: true }));
65
+ window.dispatchEvent(new KeyboardEvent("keyup"));
66
+ expect(pressed()).toBe(false);
67
+ });
68
+ });
69
+ // ---------- useIdle ----------
70
+ describe("useIdle", () => {
71
+ it("starts as not idle", () => {
72
+ vi.useFakeTimers();
73
+ const idle = useIdle(500);
74
+ expect(idle()).toBe(false);
75
+ vi.useRealTimers();
76
+ });
77
+ it("becomes idle after the timeout elapses", () => {
78
+ vi.useFakeTimers();
79
+ const idle = useIdle(500);
80
+ vi.advanceTimersByTime(500);
81
+ expect(idle()).toBe(true);
82
+ vi.useRealTimers();
83
+ });
84
+ it("resets idle on user activity", () => {
85
+ vi.useFakeTimers();
86
+ const idle = useIdle(500);
87
+ vi.advanceTimersByTime(300);
88
+ window.dispatchEvent(new MouseEvent("mousemove"));
89
+ vi.advanceTimersByTime(300); // not enough since activity reset the timer
90
+ expect(idle()).toBe(false);
91
+ vi.useRealTimers();
92
+ });
93
+ });
94
+ //# sourceMappingURL=browser.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.test.js","sourceRoot":"","sources":["../../src/__tests__/browser.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dom.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/dom.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,84 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect } from "vitest";
3
+ import { createRef, useWindowSize, useScrollPosition, useFocus } from "../dom";
4
+ // ---------- createRef ----------
5
+ describe("createRef", () => {
6
+ it("starts with current = null", () => {
7
+ const ref = createRef();
8
+ expect(ref.current).toBeNull();
9
+ });
10
+ it("can be assigned an element", () => {
11
+ const ref = createRef();
12
+ const el = document.createElement("div");
13
+ ref.current = el;
14
+ expect(ref.current).toBe(el);
15
+ });
16
+ });
17
+ // ---------- useWindowSize ----------
18
+ describe("useWindowSize", () => {
19
+ it("returns the current window dimensions", () => {
20
+ Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1024 });
21
+ Object.defineProperty(window, "innerHeight", { writable: true, configurable: true, value: 768 });
22
+ const { width, height } = useWindowSize();
23
+ expect(width()).toBe(1024);
24
+ expect(height()).toBe(768);
25
+ });
26
+ it("updates on resize event", () => {
27
+ Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 800 });
28
+ Object.defineProperty(window, "innerHeight", { writable: true, configurable: true, value: 600 });
29
+ const { width, height } = useWindowSize();
30
+ Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1280 });
31
+ Object.defineProperty(window, "innerHeight", { writable: true, configurable: true, value: 720 });
32
+ window.dispatchEvent(new Event("resize"));
33
+ expect(width()).toBe(1280);
34
+ expect(height()).toBe(720);
35
+ });
36
+ });
37
+ // ---------- useScrollPosition ----------
38
+ describe("useScrollPosition", () => {
39
+ it("starts at (0, 0)", () => {
40
+ const { x, y } = useScrollPosition(window);
41
+ expect(x()).toBe(0);
42
+ expect(y()).toBe(0);
43
+ });
44
+ it("updates on element scroll", () => {
45
+ const el = document.createElement("div");
46
+ const { x, y } = useScrollPosition(el);
47
+ Object.defineProperty(el, "scrollLeft", { configurable: true, value: 50 });
48
+ Object.defineProperty(el, "scrollTop", { configurable: true, value: 80 });
49
+ el.dispatchEvent(new Event("scroll"));
50
+ expect(x()).toBe(50);
51
+ expect(y()).toBe(80);
52
+ });
53
+ });
54
+ // ---------- useFocus ----------
55
+ describe("useFocus", () => {
56
+ it("starts as not focused", () => {
57
+ const el = document.createElement("input");
58
+ document.body.appendChild(el);
59
+ const ref = { current: el };
60
+ const focused = useFocus(ref);
61
+ expect(focused()).toBe(false);
62
+ document.body.removeChild(el);
63
+ });
64
+ it("becomes true on focus event", () => {
65
+ const el = document.createElement("input");
66
+ document.body.appendChild(el);
67
+ const ref = { current: el };
68
+ const focused = useFocus(ref);
69
+ el.dispatchEvent(new FocusEvent("focus"));
70
+ expect(focused()).toBe(true);
71
+ document.body.removeChild(el);
72
+ });
73
+ it("becomes false on blur event", () => {
74
+ const el = document.createElement("input");
75
+ document.body.appendChild(el);
76
+ const ref = { current: el };
77
+ const focused = useFocus(ref);
78
+ el.dispatchEvent(new FocusEvent("focus"));
79
+ el.dispatchEvent(new FocusEvent("blur"));
80
+ expect(focused()).toBe(false);
81
+ document.body.removeChild(el);
82
+ });
83
+ });
84
+ //# sourceMappingURL=dom.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.test.js","sourceRoot":"","sources":["../../src/__tests__/dom.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=utilities.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilities.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/utilities.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,226 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
+ import { signal } from "@praxisjs/core/internal";
4
+ import { usePagination, useClipboard, useGeolocation, useTimeAgo } from "../utilities";
5
+ describe("usePagination", () => {
6
+ it("starts on page 1 by default", () => {
7
+ const p = usePagination({ total: 100, pageSize: 10 });
8
+ expect(p.page()).toBe(1);
9
+ });
10
+ it("respects initial page option", () => {
11
+ const p = usePagination({ total: 100, pageSize: 10, initial: 3 });
12
+ expect(p.page()).toBe(3);
13
+ });
14
+ it("computes total pages correctly", () => {
15
+ expect(usePagination({ total: 100, pageSize: 10 }).totalPages()).toBe(10);
16
+ expect(usePagination({ total: 101, pageSize: 10 }).totalPages()).toBe(11);
17
+ expect(usePagination({ total: 5, pageSize: 10 }).totalPages()).toBe(1);
18
+ });
19
+ it("computes offset", () => {
20
+ const p = usePagination({ total: 100, pageSize: 10 });
21
+ expect(p.offset()).toBe(0);
22
+ p.goTo(3);
23
+ expect(p.offset()).toBe(20);
24
+ });
25
+ it("next() advances the page", () => {
26
+ const p = usePagination({ total: 30, pageSize: 10 });
27
+ p.next();
28
+ expect(p.page()).toBe(2);
29
+ });
30
+ it("next() does nothing on last page", () => {
31
+ const p = usePagination({ total: 10, pageSize: 10 });
32
+ p.next();
33
+ expect(p.page()).toBe(1);
34
+ });
35
+ it("prev() goes back", () => {
36
+ const p = usePagination({ total: 30, pageSize: 10, initial: 3 });
37
+ p.prev();
38
+ expect(p.page()).toBe(2);
39
+ });
40
+ it("prev() does nothing on first page", () => {
41
+ const p = usePagination({ total: 30, pageSize: 10 });
42
+ p.prev();
43
+ expect(p.page()).toBe(1);
44
+ });
45
+ it("goTo() clamps to valid range", () => {
46
+ const p = usePagination({ total: 30, pageSize: 10 });
47
+ p.goTo(0);
48
+ expect(p.page()).toBe(1);
49
+ p.goTo(99);
50
+ expect(p.page()).toBe(3);
51
+ });
52
+ it("first() jumps to page 1", () => {
53
+ const p = usePagination({ total: 50, pageSize: 10, initial: 5 });
54
+ p.first();
55
+ expect(p.page()).toBe(1);
56
+ });
57
+ it("last() jumps to last page", () => {
58
+ const p = usePagination({ total: 50, pageSize: 10 });
59
+ p.last();
60
+ expect(p.page()).toBe(5);
61
+ });
62
+ it("hasNext is false on last page", () => {
63
+ const p = usePagination({ total: 10, pageSize: 10 });
64
+ expect(p.hasNext()).toBe(false);
65
+ });
66
+ it("hasPrev is false on first page", () => {
67
+ const p = usePagination({ total: 30, pageSize: 10 });
68
+ expect(p.hasPrev()).toBe(false);
69
+ });
70
+ it("pages() returns array of page numbers", () => {
71
+ const p = usePagination({ total: 30, pageSize: 10 });
72
+ expect(p.pages()).toEqual([1, 2, 3]);
73
+ });
74
+ it("pageSize() reflects the configured size", () => {
75
+ const p = usePagination({ total: 100, pageSize: 25 });
76
+ expect(p.pageSize()).toBe(25);
77
+ });
78
+ it("accepts a factory function for options", () => {
79
+ const p = usePagination(() => ({ total: 50, pageSize: 5 }));
80
+ expect(p.totalPages()).toBe(10);
81
+ });
82
+ });
83
+ // ── useClipboard ──────────────────────────────────────────────────────────────
84
+ describe("useClipboard", () => {
85
+ beforeEach(() => {
86
+ vi.useFakeTimers();
87
+ Object.defineProperty(navigator, "clipboard", {
88
+ value: { writeText: vi.fn().mockResolvedValue(undefined) },
89
+ configurable: true,
90
+ writable: true,
91
+ });
92
+ });
93
+ afterEach(() => {
94
+ vi.clearAllTimers();
95
+ vi.useRealTimers();
96
+ });
97
+ it("copied starts as false", () => {
98
+ const { copied } = useClipboard();
99
+ expect(copied()).toBe(false);
100
+ });
101
+ it("content starts as empty string", () => {
102
+ const { content } = useClipboard();
103
+ expect(content()).toBe("");
104
+ });
105
+ it("sets copied to true and content to the text after copy()", async () => {
106
+ const { copy, copied, content } = useClipboard();
107
+ await copy("hello");
108
+ expect(copied()).toBe(true);
109
+ expect(content()).toBe("hello");
110
+ });
111
+ it("resets copied to false after resetDelay", async () => {
112
+ const { copy, copied } = useClipboard(1000);
113
+ await copy("test");
114
+ expect(copied()).toBe(true);
115
+ vi.advanceTimersByTime(1000);
116
+ expect(copied()).toBe(false);
117
+ });
118
+ it("warns and does not update state on clipboard failure", async () => {
119
+ navigator.clipboard.writeText =
120
+ vi.fn().mockRejectedValue(new Error("denied"));
121
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
122
+ const { copy, copied } = useClipboard();
123
+ await copy("fail");
124
+ expect(copied()).toBe(false);
125
+ expect(warn).toHaveBeenCalled();
126
+ warn.mockRestore();
127
+ });
128
+ });
129
+ // ── useGeolocation ────────────────────────────────────────────────────────────
130
+ describe("useGeolocation", () => {
131
+ it("starts with loading=true and lat/lng null", () => {
132
+ const successCb = [];
133
+ Object.defineProperty(navigator, "geolocation", {
134
+ value: {
135
+ getCurrentPosition: (success) => {
136
+ successCb.push(success);
137
+ },
138
+ },
139
+ configurable: true,
140
+ });
141
+ const geo = useGeolocation();
142
+ expect(geo.loading()).toBe(true);
143
+ expect(geo.lat()).toBeNull();
144
+ expect(geo.lng()).toBeNull();
145
+ });
146
+ it("updates lat/lng/loading on success", () => {
147
+ let successCb = null;
148
+ Object.defineProperty(navigator, "geolocation", {
149
+ value: {
150
+ getCurrentPosition: (success) => {
151
+ successCb = success;
152
+ },
153
+ },
154
+ configurable: true,
155
+ });
156
+ const geo = useGeolocation();
157
+ successCb({
158
+ coords: { latitude: 10.5, longitude: 20.5 },
159
+ });
160
+ expect(geo.lat()).toBe(10.5);
161
+ expect(geo.lng()).toBe(20.5);
162
+ expect(geo.loading()).toBe(false);
163
+ });
164
+ it("sets error and loading=false on failure", () => {
165
+ let errorCb = null;
166
+ Object.defineProperty(navigator, "geolocation", {
167
+ value: {
168
+ getCurrentPosition: (_s, error) => {
169
+ errorCb = error;
170
+ },
171
+ },
172
+ configurable: true,
173
+ });
174
+ const geo = useGeolocation();
175
+ const fakeError = { code: 1, message: "denied" };
176
+ errorCb(fakeError);
177
+ expect(geo.error()).toBe(fakeError);
178
+ expect(geo.loading()).toBe(false);
179
+ });
180
+ });
181
+ // ── useTimeAgo ────────────────────────────────────────────────────────────────
182
+ describe("useTimeAgo", () => {
183
+ beforeEach(() => {
184
+ vi.useFakeTimers();
185
+ });
186
+ afterEach(() => {
187
+ vi.clearAllTimers();
188
+ vi.useRealTimers();
189
+ });
190
+ it("returns a computed string", () => {
191
+ const now = Date.now();
192
+ const t = useTimeAgo(() => now);
193
+ expect(typeof t()).toBe("string");
194
+ });
195
+ it("formats seconds ago", () => {
196
+ const now = Date.now();
197
+ const t = useTimeAgo(() => now - 30_000);
198
+ expect(t()).toMatch(/segundo|second/i);
199
+ });
200
+ it("formats minutes ago", () => {
201
+ const now = Date.now();
202
+ const t = useTimeAgo(() => now - 5 * 60_000);
203
+ expect(t()).toMatch(/minuto|minute/i);
204
+ });
205
+ it("formats hours ago", () => {
206
+ const now = Date.now();
207
+ const t = useTimeAgo(() => now - 2 * 3_600_000);
208
+ expect(t()).toMatch(/hora|hour/i);
209
+ });
210
+ it("formats days ago", () => {
211
+ const now = Date.now();
212
+ const t = useTimeAgo(() => now - 3 * 86_400_000);
213
+ expect(t()).toMatch(/dia|day/i);
214
+ });
215
+ it("formats months ago", () => {
216
+ const now = Date.now();
217
+ const t = useTimeAgo(() => now - 60 * 86_400_000);
218
+ expect(t()).toMatch(/mes|month/i);
219
+ });
220
+ it("accepts a signal source", () => {
221
+ const src = signal(Date.now() - 90_000);
222
+ const t = useTimeAgo(src);
223
+ expect(typeof t()).toBe("string");
224
+ });
225
+ });
226
+ //# sourceMappingURL=utilities.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilities.test.js","sourceRoot":"","sources":["../../src/__tests__/utilities.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisjs/composables",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  "typescript": "^5.9.3"
15
15
  },
16
16
  "dependencies": {
17
- "@praxisjs/core": "0.3.0",
17
+ "@praxisjs/core": "0.4.1",
18
18
  "@praxisjs/shared": "0.2.0"
19
19
  },
20
20
  "scripts": {
@@ -0,0 +1,118 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi } from "vitest";
3
+
4
+ import { useMediaQuery, useColorScheme, useMouse, useKeyCombo, useIdle } from "../browser";
5
+
6
+ // ---------- useMediaQuery ----------
7
+
8
+ describe("useMediaQuery", () => {
9
+ it("returns computed that reflects matchMedia result", () => {
10
+ Object.defineProperty(window, "matchMedia", {
11
+ writable: true,
12
+ value: vi.fn((query: string) => ({
13
+ matches: query === "(prefers-color-scheme: dark)",
14
+ addEventListener: vi.fn(),
15
+ removeEventListener: vi.fn(),
16
+ })),
17
+ });
18
+
19
+ const isDark = useMediaQuery("(prefers-color-scheme: dark)");
20
+ expect(isDark()).toBe(true);
21
+
22
+ const isLight = useMediaQuery("(prefers-color-scheme: light)");
23
+ expect(isLight()).toBe(false);
24
+ });
25
+ });
26
+
27
+ // ---------- useColorScheme ----------
28
+
29
+ describe("useColorScheme", () => {
30
+ it("isDark and isLight are complementary", () => {
31
+ Object.defineProperty(window, "matchMedia", {
32
+ writable: true,
33
+ value: vi.fn(() => ({
34
+ matches: true,
35
+ addEventListener: vi.fn(),
36
+ removeEventListener: vi.fn(),
37
+ })),
38
+ });
39
+
40
+ const { isDark, isLight } = useColorScheme();
41
+ expect(isDark()).toBe(true);
42
+ expect(isLight()).toBe(false);
43
+ });
44
+ });
45
+
46
+ // ---------- useMouse ----------
47
+
48
+ describe("useMouse", () => {
49
+ it("starts at (0, 0)", () => {
50
+ const { x, y } = useMouse();
51
+ expect(x()).toBe(0);
52
+ expect(y()).toBe(0);
53
+ });
54
+
55
+ it("updates on mousemove event", () => {
56
+ const { x, y } = useMouse();
57
+ window.dispatchEvent(
58
+ new MouseEvent("mousemove", { clientX: 100, clientY: 200 }),
59
+ );
60
+ expect(x()).toBe(100);
61
+ expect(y()).toBe(200);
62
+ });
63
+ });
64
+
65
+ // ---------- useKeyCombo ----------
66
+
67
+ describe("useKeyCombo", () => {
68
+ it("starts as false", () => {
69
+ const pressed = useKeyCombo("ctrl+s");
70
+ expect(pressed()).toBe(false);
71
+ });
72
+
73
+ it("becomes true on matching keydown", () => {
74
+ const pressed = useKeyCombo("ctrl+s");
75
+ window.dispatchEvent(
76
+ new KeyboardEvent("keydown", { key: "s", ctrlKey: true }),
77
+ );
78
+ expect(pressed()).toBe(true);
79
+ });
80
+
81
+ it("resets to false on keyup", () => {
82
+ const pressed = useKeyCombo("ctrl+s");
83
+ window.dispatchEvent(
84
+ new KeyboardEvent("keydown", { key: "s", ctrlKey: true }),
85
+ );
86
+ window.dispatchEvent(new KeyboardEvent("keyup"));
87
+ expect(pressed()).toBe(false);
88
+ });
89
+ });
90
+
91
+ // ---------- useIdle ----------
92
+
93
+ describe("useIdle", () => {
94
+ it("starts as not idle", () => {
95
+ vi.useFakeTimers();
96
+ const idle = useIdle(500);
97
+ expect(idle()).toBe(false);
98
+ vi.useRealTimers();
99
+ });
100
+
101
+ it("becomes idle after the timeout elapses", () => {
102
+ vi.useFakeTimers();
103
+ const idle = useIdle(500);
104
+ vi.advanceTimersByTime(500);
105
+ expect(idle()).toBe(true);
106
+ vi.useRealTimers();
107
+ });
108
+
109
+ it("resets idle on user activity", () => {
110
+ vi.useFakeTimers();
111
+ const idle = useIdle(500);
112
+ vi.advanceTimersByTime(300);
113
+ window.dispatchEvent(new MouseEvent("mousemove"));
114
+ vi.advanceTimersByTime(300); // not enough since activity reset the timer
115
+ expect(idle()).toBe(false);
116
+ vi.useRealTimers();
117
+ });
118
+ });
@@ -0,0 +1,103 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect } from "vitest";
3
+
4
+ import { createRef, useWindowSize, useScrollPosition, useFocus } from "../dom";
5
+
6
+ // ---------- createRef ----------
7
+
8
+ describe("createRef", () => {
9
+ it("starts with current = null", () => {
10
+ const ref = createRef();
11
+ expect(ref.current).toBeNull();
12
+ });
13
+
14
+ it("can be assigned an element", () => {
15
+ const ref = createRef();
16
+ const el = document.createElement("div");
17
+ ref.current = el;
18
+ expect(ref.current).toBe(el);
19
+ });
20
+ });
21
+
22
+ // ---------- useWindowSize ----------
23
+
24
+ describe("useWindowSize", () => {
25
+ it("returns the current window dimensions", () => {
26
+ Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1024 });
27
+ Object.defineProperty(window, "innerHeight", { writable: true, configurable: true, value: 768 });
28
+
29
+ const { width, height } = useWindowSize();
30
+ expect(width()).toBe(1024);
31
+ expect(height()).toBe(768);
32
+ });
33
+
34
+ it("updates on resize event", () => {
35
+ Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 800 });
36
+ Object.defineProperty(window, "innerHeight", { writable: true, configurable: true, value: 600 });
37
+
38
+ const { width, height } = useWindowSize();
39
+
40
+ Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1280 });
41
+ Object.defineProperty(window, "innerHeight", { writable: true, configurable: true, value: 720 });
42
+ window.dispatchEvent(new Event("resize"));
43
+
44
+ expect(width()).toBe(1280);
45
+ expect(height()).toBe(720);
46
+ });
47
+ });
48
+
49
+ // ---------- useScrollPosition ----------
50
+
51
+ describe("useScrollPosition", () => {
52
+ it("starts at (0, 0)", () => {
53
+ const { x, y } = useScrollPosition(window);
54
+ expect(x()).toBe(0);
55
+ expect(y()).toBe(0);
56
+ });
57
+
58
+ it("updates on element scroll", () => {
59
+ const el = document.createElement("div");
60
+ const { x, y } = useScrollPosition(el);
61
+
62
+ Object.defineProperty(el, "scrollLeft", { configurable: true, value: 50 });
63
+ Object.defineProperty(el, "scrollTop", { configurable: true, value: 80 });
64
+ el.dispatchEvent(new Event("scroll"));
65
+
66
+ expect(x()).toBe(50);
67
+ expect(y()).toBe(80);
68
+ });
69
+ });
70
+
71
+ // ---------- useFocus ----------
72
+
73
+ describe("useFocus", () => {
74
+ it("starts as not focused", () => {
75
+ const el = document.createElement("input");
76
+ document.body.appendChild(el);
77
+ const ref = { current: el };
78
+ const focused = useFocus(ref);
79
+ expect(focused()).toBe(false);
80
+ document.body.removeChild(el);
81
+ });
82
+
83
+ it("becomes true on focus event", () => {
84
+ const el = document.createElement("input");
85
+ document.body.appendChild(el);
86
+ const ref = { current: el };
87
+ const focused = useFocus(ref);
88
+ el.dispatchEvent(new FocusEvent("focus"));
89
+ expect(focused()).toBe(true);
90
+ document.body.removeChild(el);
91
+ });
92
+
93
+ it("becomes false on blur event", () => {
94
+ const el = document.createElement("input");
95
+ document.body.appendChild(el);
96
+ const ref = { current: el };
97
+ const focused = useFocus(ref);
98
+ el.dispatchEvent(new FocusEvent("focus"));
99
+ el.dispatchEvent(new FocusEvent("blur"));
100
+ expect(focused()).toBe(false);
101
+ document.body.removeChild(el);
102
+ });
103
+ });
@@ -0,0 +1,271 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
+
4
+ import { signal } from "@praxisjs/core/internal";
5
+
6
+ import { usePagination, useClipboard, useGeolocation, useTimeAgo } from "../utilities";
7
+
8
+ describe("usePagination", () => {
9
+ it("starts on page 1 by default", () => {
10
+ const p = usePagination({ total: 100, pageSize: 10 });
11
+ expect(p.page()).toBe(1);
12
+ });
13
+
14
+ it("respects initial page option", () => {
15
+ const p = usePagination({ total: 100, pageSize: 10, initial: 3 });
16
+ expect(p.page()).toBe(3);
17
+ });
18
+
19
+ it("computes total pages correctly", () => {
20
+ expect(usePagination({ total: 100, pageSize: 10 }).totalPages()).toBe(10);
21
+ expect(usePagination({ total: 101, pageSize: 10 }).totalPages()).toBe(11);
22
+ expect(usePagination({ total: 5, pageSize: 10 }).totalPages()).toBe(1);
23
+ });
24
+
25
+ it("computes offset", () => {
26
+ const p = usePagination({ total: 100, pageSize: 10 });
27
+ expect(p.offset()).toBe(0);
28
+ p.goTo(3);
29
+ expect(p.offset()).toBe(20);
30
+ });
31
+
32
+ it("next() advances the page", () => {
33
+ const p = usePagination({ total: 30, pageSize: 10 });
34
+ p.next();
35
+ expect(p.page()).toBe(2);
36
+ });
37
+
38
+ it("next() does nothing on last page", () => {
39
+ const p = usePagination({ total: 10, pageSize: 10 });
40
+ p.next();
41
+ expect(p.page()).toBe(1);
42
+ });
43
+
44
+ it("prev() goes back", () => {
45
+ const p = usePagination({ total: 30, pageSize: 10, initial: 3 });
46
+ p.prev();
47
+ expect(p.page()).toBe(2);
48
+ });
49
+
50
+ it("prev() does nothing on first page", () => {
51
+ const p = usePagination({ total: 30, pageSize: 10 });
52
+ p.prev();
53
+ expect(p.page()).toBe(1);
54
+ });
55
+
56
+ it("goTo() clamps to valid range", () => {
57
+ const p = usePagination({ total: 30, pageSize: 10 });
58
+ p.goTo(0);
59
+ expect(p.page()).toBe(1);
60
+ p.goTo(99);
61
+ expect(p.page()).toBe(3);
62
+ });
63
+
64
+ it("first() jumps to page 1", () => {
65
+ const p = usePagination({ total: 50, pageSize: 10, initial: 5 });
66
+ p.first();
67
+ expect(p.page()).toBe(1);
68
+ });
69
+
70
+ it("last() jumps to last page", () => {
71
+ const p = usePagination({ total: 50, pageSize: 10 });
72
+ p.last();
73
+ expect(p.page()).toBe(5);
74
+ });
75
+
76
+ it("hasNext is false on last page", () => {
77
+ const p = usePagination({ total: 10, pageSize: 10 });
78
+ expect(p.hasNext()).toBe(false);
79
+ });
80
+
81
+ it("hasPrev is false on first page", () => {
82
+ const p = usePagination({ total: 30, pageSize: 10 });
83
+ expect(p.hasPrev()).toBe(false);
84
+ });
85
+
86
+ it("pages() returns array of page numbers", () => {
87
+ const p = usePagination({ total: 30, pageSize: 10 });
88
+ expect(p.pages()).toEqual([1, 2, 3]);
89
+ });
90
+
91
+ it("pageSize() reflects the configured size", () => {
92
+ const p = usePagination({ total: 100, pageSize: 25 });
93
+ expect(p.pageSize()).toBe(25);
94
+ });
95
+
96
+ it("accepts a factory function for options", () => {
97
+ const p = usePagination(() => ({ total: 50, pageSize: 5 }));
98
+ expect(p.totalPages()).toBe(10);
99
+ });
100
+ });
101
+
102
+ // ── useClipboard ──────────────────────────────────────────────────────────────
103
+
104
+ describe("useClipboard", () => {
105
+ beforeEach(() => {
106
+ vi.useFakeTimers();
107
+ Object.defineProperty(navigator, "clipboard", {
108
+ value: { writeText: vi.fn().mockResolvedValue(undefined) },
109
+ configurable: true,
110
+ writable: true,
111
+ });
112
+ });
113
+
114
+ afterEach(() => {
115
+ vi.clearAllTimers();
116
+ vi.useRealTimers();
117
+ });
118
+
119
+ it("copied starts as false", () => {
120
+ const { copied } = useClipboard();
121
+ expect(copied()).toBe(false);
122
+ });
123
+
124
+ it("content starts as empty string", () => {
125
+ const { content } = useClipboard();
126
+ expect(content()).toBe("");
127
+ });
128
+
129
+ it("sets copied to true and content to the text after copy()", async () => {
130
+ const { copy, copied, content } = useClipboard();
131
+ await copy("hello");
132
+ expect(copied()).toBe(true);
133
+ expect(content()).toBe("hello");
134
+ });
135
+
136
+ it("resets copied to false after resetDelay", async () => {
137
+ const { copy, copied } = useClipboard(1000);
138
+ await copy("test");
139
+ expect(copied()).toBe(true);
140
+ vi.advanceTimersByTime(1000);
141
+ expect(copied()).toBe(false);
142
+ });
143
+
144
+ it("warns and does not update state on clipboard failure", async () => {
145
+ (navigator.clipboard as unknown as { writeText: ReturnType<typeof vi.fn> }).writeText =
146
+ vi.fn().mockRejectedValue(new Error("denied"));
147
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
148
+ const { copy, copied } = useClipboard();
149
+ await copy("fail");
150
+ expect(copied()).toBe(false);
151
+ expect(warn).toHaveBeenCalled();
152
+ warn.mockRestore();
153
+ });
154
+ });
155
+
156
+ // ── useGeolocation ────────────────────────────────────────────────────────────
157
+
158
+ describe("useGeolocation", () => {
159
+ it("starts with loading=true and lat/lng null", () => {
160
+ const successCb: PositionCallback[] = [];
161
+ Object.defineProperty(navigator, "geolocation", {
162
+ value: {
163
+ getCurrentPosition: (success: PositionCallback) => {
164
+ successCb.push(success);
165
+ },
166
+ },
167
+ configurable: true,
168
+ });
169
+
170
+ const geo = useGeolocation();
171
+ expect(geo.loading()).toBe(true);
172
+ expect(geo.lat()).toBeNull();
173
+ expect(geo.lng()).toBeNull();
174
+ });
175
+
176
+ it("updates lat/lng/loading on success", () => {
177
+ let successCb: PositionCallback | null = null;
178
+ Object.defineProperty(navigator, "geolocation", {
179
+ value: {
180
+ getCurrentPosition: (success: PositionCallback) => {
181
+ successCb = success;
182
+ },
183
+ },
184
+ configurable: true,
185
+ });
186
+
187
+ const geo = useGeolocation();
188
+
189
+ successCb!({
190
+ coords: { latitude: 10.5, longitude: 20.5 } as GeolocationCoordinates,
191
+ } as GeolocationPosition);
192
+
193
+ expect(geo.lat()).toBe(10.5);
194
+ expect(geo.lng()).toBe(20.5);
195
+ expect(geo.loading()).toBe(false);
196
+ });
197
+
198
+ it("sets error and loading=false on failure", () => {
199
+ let errorCb: PositionErrorCallback | null = null;
200
+ Object.defineProperty(navigator, "geolocation", {
201
+ value: {
202
+ getCurrentPosition: (_s: PositionCallback, error: PositionErrorCallback) => {
203
+ errorCb = error;
204
+ },
205
+ },
206
+ configurable: true,
207
+ });
208
+
209
+ const geo = useGeolocation();
210
+ const fakeError = { code: 1, message: "denied" } as GeolocationPositionError;
211
+ errorCb!(fakeError);
212
+
213
+ expect(geo.error()).toBe(fakeError);
214
+ expect(geo.loading()).toBe(false);
215
+ });
216
+ });
217
+
218
+ // ── useTimeAgo ────────────────────────────────────────────────────────────────
219
+
220
+ describe("useTimeAgo", () => {
221
+ beforeEach(() => {
222
+ vi.useFakeTimers();
223
+ });
224
+
225
+ afterEach(() => {
226
+ vi.clearAllTimers();
227
+ vi.useRealTimers();
228
+ });
229
+
230
+ it("returns a computed string", () => {
231
+ const now = Date.now();
232
+ const t = useTimeAgo(() => now);
233
+ expect(typeof t()).toBe("string");
234
+ });
235
+
236
+ it("formats seconds ago", () => {
237
+ const now = Date.now();
238
+ const t = useTimeAgo(() => now - 30_000);
239
+ expect(t()).toMatch(/segundo|second/i);
240
+ });
241
+
242
+ it("formats minutes ago", () => {
243
+ const now = Date.now();
244
+ const t = useTimeAgo(() => now - 5 * 60_000);
245
+ expect(t()).toMatch(/minuto|minute/i);
246
+ });
247
+
248
+ it("formats hours ago", () => {
249
+ const now = Date.now();
250
+ const t = useTimeAgo(() => now - 2 * 3_600_000);
251
+ expect(t()).toMatch(/hora|hour/i);
252
+ });
253
+
254
+ it("formats days ago", () => {
255
+ const now = Date.now();
256
+ const t = useTimeAgo(() => now - 3 * 86_400_000);
257
+ expect(t()).toMatch(/dia|day/i);
258
+ });
259
+
260
+ it("formats months ago", () => {
261
+ const now = Date.now();
262
+ const t = useTimeAgo(() => now - 60 * 86_400_000);
263
+ expect(t()).toMatch(/mes|month/i);
264
+ });
265
+
266
+ it("accepts a signal source", () => {
267
+ const src = signal(Date.now() - 90_000);
268
+ const t = useTimeAgo(src);
269
+ expect(typeof t()).toBe("string");
270
+ });
271
+ });