rask-ui 0.28.2 → 0.28.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.
Files changed (43) hide show
  1. package/dist/component.d.ts +1 -0
  2. package/dist/component.d.ts.map +1 -1
  3. package/dist/component.js +7 -2
  4. package/dist/tests/batch.test.js +202 -12
  5. package/dist/tests/createContext.test.js +50 -37
  6. package/dist/tests/error.test.js +25 -12
  7. package/dist/tests/renderCount.test.d.ts +2 -0
  8. package/dist/tests/renderCount.test.d.ts.map +1 -0
  9. package/dist/tests/renderCount.test.js +95 -0
  10. package/dist/tests/scopeEnforcement.test.d.ts +2 -0
  11. package/dist/tests/scopeEnforcement.test.d.ts.map +1 -0
  12. package/dist/tests/scopeEnforcement.test.js +157 -0
  13. package/dist/tests/useAction.test.d.ts +2 -0
  14. package/dist/tests/useAction.test.d.ts.map +1 -0
  15. package/dist/tests/useAction.test.js +132 -0
  16. package/dist/tests/useAsync.test.d.ts +2 -0
  17. package/dist/tests/useAsync.test.d.ts.map +1 -0
  18. package/dist/tests/useAsync.test.js +499 -0
  19. package/dist/tests/useDerived.test.d.ts +2 -0
  20. package/dist/tests/useDerived.test.d.ts.map +1 -0
  21. package/dist/tests/useDerived.test.js +407 -0
  22. package/dist/tests/useEffect.test.d.ts +2 -0
  23. package/dist/tests/useEffect.test.d.ts.map +1 -0
  24. package/dist/tests/useEffect.test.js +600 -0
  25. package/dist/tests/useLookup.test.d.ts +2 -0
  26. package/dist/tests/useLookup.test.d.ts.map +1 -0
  27. package/dist/tests/useLookup.test.js +299 -0
  28. package/dist/tests/useRef.test.d.ts +2 -0
  29. package/dist/tests/useRef.test.d.ts.map +1 -0
  30. package/dist/tests/useRef.test.js +189 -0
  31. package/dist/tests/useState.test.d.ts +2 -0
  32. package/dist/tests/useState.test.d.ts.map +1 -0
  33. package/dist/tests/useState.test.js +178 -0
  34. package/dist/tests/useSuspend.test.d.ts +2 -0
  35. package/dist/tests/useSuspend.test.d.ts.map +1 -0
  36. package/dist/tests/useSuspend.test.js +752 -0
  37. package/dist/tests/useView.test.d.ts +2 -0
  38. package/dist/tests/useView.test.d.ts.map +1 -0
  39. package/dist/tests/useView.test.js +305 -0
  40. package/dist/transformer.d.ts.map +1 -1
  41. package/dist/transformer.js +1 -5
  42. package/dist/useState.js +4 -2
  43. package/package.json +1 -1
@@ -0,0 +1,95 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "rask-ui/jsx-runtime";
2
+ import { describe, it, expect } from "vitest";
3
+ import { useState } from "../useState";
4
+ import { render } from "../";
5
+ describe("Child render count test", () => {
6
+ it("should track how many times Child renders when count changes", async () => {
7
+ let childRenderCount = 0;
8
+ let state;
9
+ function Child(props) {
10
+ return () => {
11
+ childRenderCount++;
12
+ return (_jsxs("div", { children: ["State count: ", props.state.count, ", Props count: ", props.count] }));
13
+ };
14
+ }
15
+ function MyComp() {
16
+ state = useState({
17
+ count: 0,
18
+ });
19
+ return () => (_jsx("div", { children: _jsx(Child, { state: state, count: state.count }) }));
20
+ }
21
+ const container = document.createElement("div");
22
+ render(_jsx(MyComp, {}), container);
23
+ // Initial render
24
+ expect(childRenderCount).toBe(1);
25
+ expect(container.textContent).toBe("State count: 0, Props count: 0");
26
+ // Simulate click to increment count
27
+ state.count++;
28
+ await new Promise((resolve) => setTimeout(resolve, 10));
29
+ // After first increment
30
+ expect(childRenderCount).toBe(2);
31
+ expect(container.textContent).toBe("State count: 1, Props count: 1");
32
+ state.count++;
33
+ await new Promise((resolve) => setTimeout(resolve, 10));
34
+ // After second increment
35
+ expect(childRenderCount).toBe(3);
36
+ expect(container.textContent).toBe("State count: 2, Props count: 2");
37
+ });
38
+ it("should show Child renders only once when only state prop changes", async () => {
39
+ let childRenderCount = 0;
40
+ let state;
41
+ function Child(props) {
42
+ childRenderCount++;
43
+ return () => _jsxs("div", { children: ["State count: ", props.state.count] });
44
+ }
45
+ function MyComp() {
46
+ state = useState({
47
+ count: 0,
48
+ });
49
+ return () => (_jsx("div", { children: _jsx(Child, { state: state }) }));
50
+ }
51
+ const container = document.createElement("div");
52
+ render(_jsx(MyComp, {}), container);
53
+ // Initial render
54
+ expect(childRenderCount).toBe(1);
55
+ expect(container.textContent).toBe("State count: 0");
56
+ // Simulate click to increment count
57
+ state.count++;
58
+ await new Promise((resolve) => setTimeout(resolve, 10));
59
+ // Child shouldn't re-render because state object reference didn't change
60
+ expect(childRenderCount).toBe(1);
61
+ expect(container.textContent).toBe("State count: 1");
62
+ });
63
+ it("should show Child renders when primitive prop changes", async () => {
64
+ let childRenderCount = 0;
65
+ let state;
66
+ function Child(props) {
67
+ return () => {
68
+ childRenderCount++;
69
+ return _jsxs("div", { children: ["Props count: ", props.count] });
70
+ };
71
+ }
72
+ function MyComp() {
73
+ state = useState({
74
+ count: 0,
75
+ });
76
+ return () => (_jsx("div", { children: _jsx(Child, { count: state.count }) }));
77
+ }
78
+ const container = document.createElement("div");
79
+ render(_jsx(MyComp, {}), container);
80
+ // Initial render
81
+ expect(childRenderCount).toBe(1);
82
+ expect(container.textContent).toBe("Props count: 0");
83
+ // Simulate click to increment count
84
+ state.count++;
85
+ await new Promise((resolve) => setTimeout(resolve, 10));
86
+ // Child re-renders because count prop changed
87
+ expect(childRenderCount).toBe(2);
88
+ expect(container.textContent).toBe("Props count: 1");
89
+ // Another click
90
+ state.count++;
91
+ await new Promise((resolve) => setTimeout(resolve, 10));
92
+ expect(childRenderCount).toBe(3);
93
+ expect(container.textContent).toBe("Props count: 2");
94
+ });
95
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scopeEnforcement.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopeEnforcement.test.d.ts","sourceRoot":"","sources":["../../src/tests/scopeEnforcement.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,157 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "rask-ui/jsx-runtime";
2
+ import { describe, it, expect } from "vitest";
3
+ import { useState } from "../useState";
4
+ import { useEffect } from "../useEffect";
5
+ import { useDerived } from "../useDerived";
6
+ import { render } from "../index";
7
+ describe("Scope Enforcement", () => {
8
+ describe("useState", () => {
9
+ it("should allow useState in global scope", () => {
10
+ expect(() => {
11
+ const state = useState({ count: 0 });
12
+ }).not.toThrow();
13
+ });
14
+ it("should allow useState in component setup", () => {
15
+ function Component() {
16
+ const state = useState({ count: 0 });
17
+ return () => _jsx("div", { children: state.count });
18
+ }
19
+ const container = document.createElement("div");
20
+ expect(() => {
21
+ render(_jsx(Component, {}), container);
22
+ }).not.toThrow();
23
+ });
24
+ it("should throw when useState is called during render", () => {
25
+ function Component() {
26
+ const state = useState({ count: 0 });
27
+ return () => {
28
+ // This should throw - useState in render scope
29
+ expect(() => {
30
+ useState({ nested: 1 });
31
+ }).toThrow("useState cannot be called during render");
32
+ return _jsx("div", { children: state.count });
33
+ };
34
+ }
35
+ const container = document.createElement("div");
36
+ render(_jsx(Component, {}), container);
37
+ });
38
+ });
39
+ describe("useEffect", () => {
40
+ it("should throw when useEffect is called outside component setup", () => {
41
+ expect(() => {
42
+ useEffect(() => {
43
+ // Effect logic
44
+ });
45
+ }).toThrow("Only use useEffect in component setup");
46
+ });
47
+ it("should allow useEffect in component setup", () => {
48
+ function Component() {
49
+ const state = useState({ count: 0 });
50
+ let effectRan = false;
51
+ useEffect(() => {
52
+ effectRan = true;
53
+ state.count;
54
+ });
55
+ expect(effectRan).toBe(true);
56
+ return () => _jsx("div", { children: state.count });
57
+ }
58
+ const container = document.createElement("div");
59
+ expect(() => {
60
+ render(_jsx(Component, {}), container);
61
+ }).not.toThrow();
62
+ });
63
+ it("should throw when useEffect is called during render", () => {
64
+ function Component() {
65
+ const state = useState({ count: 0 });
66
+ return () => {
67
+ // This should throw - useEffect in render scope
68
+ expect(() => {
69
+ useEffect(() => {
70
+ state.count;
71
+ });
72
+ }).toThrow("Only use useEffect in component setup");
73
+ return _jsx("div", { children: state.count });
74
+ };
75
+ }
76
+ const container = document.createElement("div");
77
+ render(_jsx(Component, {}), container);
78
+ });
79
+ });
80
+ describe("useDerived", () => {
81
+ it("should throw when useDerived is called outside component setup", () => {
82
+ expect(() => {
83
+ useDerived({
84
+ doubled: () => 2 * 2,
85
+ });
86
+ }).toThrow("Only use useDerived in component setup");
87
+ });
88
+ it("should allow useDerived in component setup", () => {
89
+ function Component() {
90
+ const state = useState({ count: 5 });
91
+ const computed = useDerived({
92
+ doubled: () => state.count * 2,
93
+ });
94
+ return () => _jsx("div", { children: computed.doubled });
95
+ }
96
+ const container = document.createElement("div");
97
+ expect(() => {
98
+ render(_jsx(Component, {}), container);
99
+ }).not.toThrow();
100
+ });
101
+ it("should throw when useDerived is called during render", () => {
102
+ function Component() {
103
+ const state = useState({ count: 0 });
104
+ return () => {
105
+ // This should throw - useDerived in render scope
106
+ expect(() => {
107
+ useDerived({
108
+ doubled: () => state.count * 2,
109
+ });
110
+ }).toThrow("Only use useDerived in component setup");
111
+ return _jsx("div", { children: state.count });
112
+ };
113
+ }
114
+ const container = document.createElement("div");
115
+ render(_jsx(Component, {}), container);
116
+ });
117
+ });
118
+ describe("Mixed scenarios", () => {
119
+ it("should allow all reactive primitives in setup but not in render", () => {
120
+ function Component() {
121
+ // All of these should work in setup
122
+ const state = useState({ count: 0 });
123
+ const computed = useDerived({
124
+ doubled: () => state.count * 2,
125
+ });
126
+ useEffect(() => {
127
+ state.count;
128
+ });
129
+ return () => {
130
+ // None of these should work in render
131
+ expect(() => useState({ bad: 1 })).toThrow();
132
+ expect(() => useEffect(() => { })).toThrow();
133
+ expect(() => useDerived({ bad: () => 1 })).toThrow();
134
+ return _jsx("div", { children: computed.doubled });
135
+ };
136
+ }
137
+ const container = document.createElement("div");
138
+ render(_jsx(Component, {}), container);
139
+ });
140
+ it("should properly enforce scope in nested renders", () => {
141
+ function Child() {
142
+ const childState = useState({ value: "child" });
143
+ return () => _jsx("div", { children: childState.value });
144
+ }
145
+ function Parent() {
146
+ const parentState = useState({ value: "parent" });
147
+ return () => {
148
+ // This should fail in render
149
+ expect(() => useState({ bad: 1 })).toThrow();
150
+ return (_jsxs("div", { children: [parentState.value, _jsx(Child, {})] }));
151
+ };
152
+ }
153
+ const container = document.createElement("div");
154
+ render(_jsx(Parent, {}), container);
155
+ });
156
+ });
157
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useAction.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAction.test.d.ts","sourceRoot":"","sources":["../../src/tests/useAction.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,132 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { useAction } from "../useAction";
3
+ describe("useAction", () => {
4
+ it("should initialize with idle state", () => {
5
+ const [state] = useAction(async () => "result");
6
+ expect(state.isPending).toBe(false);
7
+ expect(state.params).toBe(null);
8
+ expect(state.result).toBe(null);
9
+ expect(state.error).toBe(null);
10
+ });
11
+ it("should handle successful action without parameters", async () => {
12
+ const fn = vi.fn(async () => "success");
13
+ const [state, run] = useAction(fn);
14
+ run();
15
+ expect(state.isPending).toBe(true);
16
+ expect(state.params).toBe(null);
17
+ expect(state.result).toBe(null);
18
+ expect(state.error).toBe(null);
19
+ await new Promise((resolve) => setTimeout(resolve, 10));
20
+ expect(state.isPending).toBe(false);
21
+ expect(state.result).toBe("success");
22
+ expect(state.error).toBe(null);
23
+ expect(fn).toHaveBeenCalledTimes(1);
24
+ });
25
+ it("should handle successful action with parameters", async () => {
26
+ const fn = vi.fn(async (x) => x * 2);
27
+ const [state, run] = useAction(fn);
28
+ run(5);
29
+ expect(state.isPending).toBe(true);
30
+ expect(state.params).toBe(5);
31
+ expect(state.result).toBe(null);
32
+ await new Promise((resolve) => setTimeout(resolve, 10));
33
+ expect(state.isPending).toBe(false);
34
+ expect(state.params).toBe(5);
35
+ expect(state.result).toBe(10);
36
+ expect(state.error).toBe(null);
37
+ });
38
+ it("should handle errors", async () => {
39
+ const fn = vi.fn(async () => {
40
+ throw new Error("Test error");
41
+ });
42
+ const [state, run] = useAction(fn);
43
+ run();
44
+ expect(state.isPending).toBe(true);
45
+ await new Promise((resolve) => setTimeout(resolve, 10));
46
+ expect(state.isPending).toBe(false);
47
+ expect(state.result).toBe(null);
48
+ expect(state.error?.message).toBe("Test error");
49
+ });
50
+ it("should abort previous action when new action starts", async () => {
51
+ let resolveFirst = null;
52
+ let resolveSecond = null;
53
+ const fn = vi.fn(async (id) => {
54
+ if (id === 1) {
55
+ return new Promise((resolve) => {
56
+ resolveFirst = resolve;
57
+ });
58
+ }
59
+ else {
60
+ return new Promise((resolve) => {
61
+ resolveSecond = resolve;
62
+ });
63
+ }
64
+ });
65
+ const [state, run] = useAction(fn);
66
+ // Start first action
67
+ run(1);
68
+ expect(state.isPending).toBe(true);
69
+ expect(state.params).toBe(1);
70
+ await new Promise((resolve) => setTimeout(resolve, 10));
71
+ // Start second action (should abort first)
72
+ run(2);
73
+ expect(state.isPending).toBe(true);
74
+ expect(state.params).toBe(2);
75
+ // Resolve first action (should be ignored due to abort)
76
+ resolveFirst?.("first");
77
+ await new Promise((resolve) => setTimeout(resolve, 10));
78
+ // State should not reflect first action
79
+ expect(state.result).toBe(null);
80
+ // Resolve second action
81
+ resolveSecond?.("second");
82
+ await new Promise((resolve) => setTimeout(resolve, 10));
83
+ // State should reflect second action
84
+ expect(state.isPending).toBe(false);
85
+ expect(state.params).toBe(2);
86
+ expect(state.result).toBe("second");
87
+ });
88
+ it("should track params even on error", async () => {
89
+ const fn = vi.fn(async (x) => {
90
+ throw new Error("Failed");
91
+ });
92
+ const [state, run] = useAction(fn);
93
+ run(42);
94
+ await new Promise((resolve) => setTimeout(resolve, 10));
95
+ expect(state.isPending).toBe(false);
96
+ expect(state.params).toBe(42);
97
+ expect(state.error?.message).toBe("Failed");
98
+ expect(state.result).toBe(null);
99
+ });
100
+ it("should handle rapid consecutive calls", async () => {
101
+ let callCount = 0;
102
+ const fn = vi.fn(async (x) => {
103
+ callCount++;
104
+ await new Promise((resolve) => setTimeout(resolve, 5));
105
+ return x;
106
+ });
107
+ const [state, run] = useAction(fn);
108
+ // Fire off multiple calls
109
+ run(1);
110
+ run(2);
111
+ run(3);
112
+ // Wait for all to settle
113
+ await new Promise((resolve) => setTimeout(resolve, 50));
114
+ // Should have called function 3 times (not aborted, just ignored results)
115
+ expect(callCount).toBe(3);
116
+ // State should reflect the last call
117
+ expect(state.isPending).toBe(false);
118
+ expect(state.params).toBe(3);
119
+ expect(state.result).toBe(3);
120
+ });
121
+ it("should handle null as valid param value", async () => {
122
+ const fn = vi.fn(async (x) => {
123
+ return x === null ? "was null" : x;
124
+ });
125
+ const [state, run] = useAction(fn);
126
+ run(null);
127
+ await new Promise((resolve) => setTimeout(resolve, 10));
128
+ expect(state.isPending).toBe(false);
129
+ expect(state.params).toBe(null);
130
+ expect(state.result).toBe("was null");
131
+ });
132
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useAsync.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAsync.test.d.ts","sourceRoot":"","sources":["../../src/tests/useAsync.test.tsx"],"names":[],"mappings":""}