stunk 2.0.0 → 2.2.0

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 (96) hide show
  1. package/LICENSE +27 -27
  2. package/README.md +43 -43
  3. package/dist/src/core/asyncChunk.d.ts +28 -0
  4. package/dist/{core → src/core}/asyncChunk.js +8 -4
  5. package/dist/{core → src/core}/computed.d.ts +5 -0
  6. package/dist/src/core/computed.js +58 -0
  7. package/dist/{core → src/core}/core.d.ts +4 -0
  8. package/dist/{core → src/core}/core.js +41 -33
  9. package/dist/src/core/selector.d.ts +17 -0
  10. package/dist/src/core/selector.js +45 -0
  11. package/dist/{core → src/core}/types.d.ts +3 -3
  12. package/dist/{use-react → src/use-react}/hooks/useAsyncChunk.d.ts +3 -3
  13. package/dist/{use-react → src/use-react}/hooks/useAsyncChunk.js +1 -1
  14. package/dist/{use-react → src/use-react}/hooks/useDerive.js +8 -2
  15. package/dist/{utils.d.ts → src/utils.d.ts} +1 -0
  16. package/dist/{utils.js → src/utils.js} +34 -0
  17. package/dist/tests/async-chunk.test.d.ts +1 -0
  18. package/dist/tests/async-chunk.test.js +164 -0
  19. package/dist/tests/batch-chunk.test.d.ts +1 -0
  20. package/dist/tests/batch-chunk.test.js +89 -0
  21. package/dist/tests/chunk.test.d.ts +1 -0
  22. package/dist/tests/chunk.test.js +215 -0
  23. package/dist/tests/computed.test.d.ts +1 -0
  24. package/dist/tests/computed.test.js +192 -0
  25. package/dist/tests/diamond-dep.test.d.ts +1 -0
  26. package/dist/tests/diamond-dep.test.js +74 -0
  27. package/dist/tests/history.test.d.ts +1 -0
  28. package/dist/tests/history.test.js +73 -0
  29. package/dist/tests/middleware.test.d.ts +1 -0
  30. package/dist/tests/middleware.test.js +30 -0
  31. package/dist/tests/persist.test.d.ts +1 -0
  32. package/dist/tests/persist.test.js +43 -0
  33. package/dist/tests/select-chunk.test.d.ts +1 -0
  34. package/dist/tests/select-chunk.test.js +167 -0
  35. package/package.json +97 -79
  36. package/dist/core/asyncChunk.d.ts +0 -22
  37. package/dist/core/computed.js +0 -42
  38. package/dist/core/selector.d.ts +0 -2
  39. package/dist/core/selector.js +0 -23
  40. package/jest.config.js +0 -4
  41. package/src/core/asyncChunk.ts +0 -83
  42. package/src/core/computed.ts +0 -65
  43. package/src/core/core.ts +0 -128
  44. package/src/core/selector.ts +0 -27
  45. package/src/core/types.ts +0 -17
  46. package/src/index.ts +0 -10
  47. package/src/middleware/history.ts +0 -113
  48. package/src/middleware/index.ts +0 -7
  49. package/src/middleware/logger.ts +0 -6
  50. package/src/middleware/persistence.ts +0 -45
  51. package/src/middleware/validator.ts +0 -8
  52. package/src/use-react/hooks/useAsyncChunk.ts +0 -38
  53. package/src/use-react/hooks/useChunk.ts +0 -40
  54. package/src/use-react/hooks/useChunkProperty.ts +0 -21
  55. package/src/use-react/hooks/useChunkValue.ts +0 -15
  56. package/src/use-react/hooks/useChunkValues.ts +0 -35
  57. package/src/use-react/hooks/useComputed.ts +0 -34
  58. package/src/use-react/hooks/useDerive.ts +0 -15
  59. package/src/use-react/index.ts +0 -9
  60. package/src/utils.ts +0 -103
  61. package/tests/async-chunk.test.ts +0 -215
  62. package/tests/batch-chunk.test.ts +0 -108
  63. package/tests/chunk.test.ts +0 -189
  64. package/tests/computed.test.ts +0 -93
  65. package/tests/history.test.ts +0 -99
  66. package/tests/middleware.test.ts +0 -37
  67. package/tests/persist.test.ts +0 -57
  68. package/tests/select-chunk.test.ts +0 -133
  69. package/tests/update.test.ts +0 -70
  70. package/tsconfig.json +0 -23
  71. /package/dist/{core → src/core}/types.js +0 -0
  72. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  73. /package/dist/{index.js → src/index.js} +0 -0
  74. /package/dist/{middleware → src/middleware}/history.d.ts +0 -0
  75. /package/dist/{middleware → src/middleware}/history.js +0 -0
  76. /package/dist/{middleware → src/middleware}/index.d.ts +0 -0
  77. /package/dist/{middleware → src/middleware}/index.js +0 -0
  78. /package/dist/{middleware → src/middleware}/logger.d.ts +0 -0
  79. /package/dist/{middleware → src/middleware}/logger.js +0 -0
  80. /package/dist/{middleware → src/middleware}/persistence.d.ts +0 -0
  81. /package/dist/{middleware → src/middleware}/persistence.js +0 -0
  82. /package/dist/{middleware → src/middleware}/validator.d.ts +0 -0
  83. /package/dist/{middleware → src/middleware}/validator.js +0 -0
  84. /package/dist/{use-react → src/use-react}/hooks/useChunk.d.ts +0 -0
  85. /package/dist/{use-react → src/use-react}/hooks/useChunk.js +0 -0
  86. /package/dist/{use-react → src/use-react}/hooks/useChunkProperty.d.ts +0 -0
  87. /package/dist/{use-react → src/use-react}/hooks/useChunkProperty.js +0 -0
  88. /package/dist/{use-react → src/use-react}/hooks/useChunkValue.d.ts +0 -0
  89. /package/dist/{use-react → src/use-react}/hooks/useChunkValue.js +0 -0
  90. /package/dist/{use-react → src/use-react}/hooks/useChunkValues.d.ts +0 -0
  91. /package/dist/{use-react → src/use-react}/hooks/useChunkValues.js +0 -0
  92. /package/dist/{use-react → src/use-react}/hooks/useComputed.d.ts +0 -0
  93. /package/dist/{use-react → src/use-react}/hooks/useComputed.js +0 -0
  94. /package/dist/{use-react → src/use-react}/hooks/useDerive.d.ts +0 -0
  95. /package/dist/{use-react → src/use-react}/index.d.ts +0 -0
  96. /package/dist/{use-react → src/use-react}/index.js +0 -0
@@ -1,189 +0,0 @@
1
- import { chunk } from "../src/core/core";
2
-
3
- test("Chunk should get and set values correctly", () => {
4
- const chunky = chunk<number>(0);
5
- expect(chunky.get()).toBe(0);
6
- chunky.set(10);
7
- expect(chunky.get()).toBe(10);
8
- chunky.set((prev) => prev + 1);
9
- expect(chunky.get()).toBe(11);
10
- });
11
-
12
- test("Chunk should notify subscribers on value change", () => {
13
- const chunky = chunk<number>(0);
14
- const callback = jest.fn();
15
- const unsubscribe = chunky.subscribe(callback); // Store unsubscribe function
16
-
17
- chunky.set(5);
18
- expect(callback).toHaveBeenCalledWith(5);
19
-
20
- chunky.set(10);
21
- expect(callback).toHaveBeenCalledWith(10);
22
-
23
- unsubscribe(); // Ensure cleanup after test
24
- });
25
-
26
- test("Chunk should notify multiple subscribers correctly", () => {
27
- const chunky = chunk<number>(0);
28
- const callback1 = jest.fn();
29
- const callback2 = jest.fn();
30
-
31
- const unsubscribe1 = chunky.subscribe(callback1);
32
- const unsubscribe2 = chunky.subscribe(callback2);
33
-
34
-
35
- chunky.set(10);
36
-
37
- expect(callback1).toHaveBeenCalledWith(10);
38
- expect(callback2).toHaveBeenCalledWith(10);
39
-
40
- unsubscribe1();
41
- unsubscribe2();
42
- });
43
-
44
-
45
- test("Chunk should allow unsubscribing from updates", () => {
46
- const chunky = chunk<number>(0);
47
- const callback = jest.fn();
48
- const unsubscribe = chunky.subscribe(callback);
49
-
50
- // Initial subscription call
51
- expect(callback).toHaveBeenCalledWith(0);
52
- expect(callback).toHaveBeenCalledTimes(1);
53
-
54
- chunky.set(5);
55
- expect(callback).toHaveBeenCalledWith(5);
56
- expect(callback).toHaveBeenCalledTimes(2);
57
-
58
- unsubscribe();
59
- chunky.set(10);
60
- expect(callback).toHaveBeenCalledTimes(2); // Still called only twice
61
- });
62
-
63
- describe("Chunk Derivation", () => {
64
- it("should create a derived chunk and update it when the original chunk changes", () => {
65
- const count = chunk(5);
66
- const doubleCount = count.derive((value) => value * 2);
67
-
68
- const countSpy = jest.fn();
69
- const doubleCountSpy = jest.fn();
70
-
71
- // Subscribe to both chunks
72
- count.subscribe(countSpy);
73
- doubleCount.subscribe(doubleCountSpy);
74
-
75
- // Initial values
76
- expect(count.get()).toBe(5);
77
- expect(doubleCount.get()).toBe(10);
78
- expect(countSpy).toHaveBeenCalledWith(5);
79
- expect(doubleCountSpy).toHaveBeenCalledWith(10);
80
-
81
- // Update count and verify updates
82
- count.set(10);
83
- expect(count.get()).toBe(10);
84
- expect(doubleCount.get()).toBe(20);
85
- expect(countSpy).toHaveBeenCalledWith(10);
86
- expect(doubleCountSpy).toHaveBeenCalledWith(20);
87
- });
88
-
89
- it("should not update the derived chunk if the original chunk value does not change", () => {
90
- const count = chunk(5);
91
- const doubleCount = count.derive((value) => value * 2);
92
-
93
- const doubleCountSpy = jest.fn();
94
-
95
- // Subscribe to the derived chunk
96
- doubleCount.subscribe(doubleCountSpy);
97
-
98
- // Setting the same value
99
- count.set(5);
100
- expect(doubleCount.get()).toBe(10); // Derived value should remain the same
101
- expect(doubleCountSpy).toHaveBeenCalledTimes(1); // Only initial value
102
- });
103
- });
104
-
105
-
106
- test("Chunk should reset to initial value", () => {
107
- const count = chunk(5);
108
- count.set(10);
109
- expect(count.get()).toBe(10);
110
- count.reset();
111
- expect(count.get()).toBe(5);
112
- });
113
-
114
-
115
- describe('Chunk destroy', () => {
116
- const countChunk = chunk(0);
117
- const anotherChunk = chunk(0);
118
- const countCallback = jest.fn();
119
- const anotherCallback = jest.fn();
120
-
121
- beforeEach(() => {
122
- // Reset the mocks
123
- countCallback.mockClear();
124
- anotherCallback.mockClear();
125
- });
126
-
127
- it('should stop notifying subscribers after destroy is called', () => {
128
- // Subscribe to the chunks
129
- const countUnsubscribe = countChunk.subscribe(countCallback);
130
- const anotherUnsubscribe = anotherChunk.subscribe(anotherCallback);
131
-
132
- // Verify initial subscription calls
133
- expect(countCallback).toHaveBeenCalledTimes(1);
134
- expect(countCallback).toHaveBeenCalledWith(0);
135
- expect(anotherCallback).toHaveBeenCalledTimes(1);
136
- expect(anotherCallback).toHaveBeenCalledWith(0);
137
-
138
- // Clear the mocks to start fresh
139
- countCallback.mockClear();
140
- anotherCallback.mockClear();
141
-
142
- // Cleanup subscriptions before destroy
143
- countUnsubscribe();
144
- anotherUnsubscribe();
145
-
146
- // Now destroy the chunks - no warning should appear
147
- countChunk.destroy();
148
- anotherChunk.destroy();
149
-
150
- // Try setting new values after destruction
151
- countChunk.set(30);
152
- anotherChunk.set(40);
153
-
154
- // Ensure that the subscribers were not notified after destroy
155
- expect(countCallback).toHaveBeenCalledTimes(0);
156
- expect(anotherCallback).toHaveBeenCalledTimes(0);
157
- });
158
-
159
- it('should reset to initial value after destroy', () => {
160
- // Set some values
161
- countChunk.set(10);
162
- anotherChunk.set(20);
163
-
164
- // Destroy the chunks (no subscribers at this point, so no warning)
165
- countChunk.destroy();
166
- anotherChunk.destroy();
167
-
168
- // Subscribe new callbacks after destroy
169
- const newCountCallback = jest.fn();
170
- const newAnotherCallback = jest.fn();
171
-
172
- const newCountUnsubscribe = countChunk.subscribe(newCountCallback);
173
- const newAnotherUnsubscribe = anotherChunk.subscribe(newAnotherCallback);
174
-
175
- // Should receive initial values
176
- expect(newCountCallback).toHaveBeenCalledWith(0);
177
- expect(newAnotherCallback).toHaveBeenCalledWith(0);
178
-
179
- // Cleanup
180
- newCountUnsubscribe();
181
- newAnotherUnsubscribe();
182
- });
183
-
184
- // Clean up after all tests
185
- afterAll(() => {
186
- countChunk.destroy();
187
- anotherChunk.destroy();
188
- });
189
- });
@@ -1,93 +0,0 @@
1
- import { chunk } from '../src/core/core';
2
- import { computed } from '../src/core/computed';
3
-
4
- describe('computed', () => {
5
- it('should compute the value based on dependencies', () => {
6
- const num1 = chunk(2);
7
- const num2 = chunk(3);
8
-
9
- const sum = computed([num1, num2], (a, b) => a + b);
10
-
11
- expect(sum.get()).toBe(5);
12
- });
13
-
14
- it('should recompute when a dependency changes', () => {
15
- const num1 = chunk(4);
16
- const num2 = chunk(5);
17
-
18
- const product = computed([num1, num2], (a, b) => a * b);
19
-
20
- expect(product.get()).toBe(20);
21
-
22
- num1.set(10);
23
-
24
- // Trigger recomputation
25
- expect(product.get()).toBe(50);
26
- });
27
-
28
- it('should cache the computed value until a dependency changes', () => {
29
- const num1 = chunk(1);
30
- const num2 = chunk(2);
31
-
32
- const sum = computed([num1, num2], (a, b) => a + b);
33
-
34
- const initialValue = sum.get();
35
- expect(initialValue).toBe(3);
36
-
37
- num1.set(1); // Setting to the same value, should not trigger recompute
38
- const cachedValue = sum.get();
39
- expect(cachedValue).toBe(3); // Cached value should be returned
40
- });
41
-
42
- it('should mark as dirty when a dependency changes', () => {
43
- const num1 = chunk(7);
44
- const num2 = chunk(8);
45
-
46
- const diff = computed([num1, num2], (a, b) => b - a);
47
-
48
- expect(diff.isDirty()).toBe(false);
49
-
50
- num2.set(10);
51
-
52
- expect(diff.isDirty()).toBe(true);
53
- });
54
-
55
- it('should throw error when attempting to set computed value', () => {
56
- const num1 = chunk(10);
57
- const num2 = chunk(20);
58
-
59
- const sum = computed([num1, num2], (a, b) => a + b);
60
-
61
- expect(() => sum.set(100)).toThrow('Cannot directly set a computed value');
62
- });
63
-
64
- it('should manually recompute the value', () => {
65
- const num1 = chunk(1);
66
- const num2 = chunk(2);
67
-
68
- const sum = computed([num1, num2], (a, b) => a + b);
69
-
70
- expect(sum.get()).toBe(3);
71
-
72
- num1.set(4);
73
- expect(sum.isDirty()).toBe(true);
74
-
75
- sum.recompute(); // Manually recompute
76
- expect(sum.get()).toBe(6);
77
- expect(sum.isDirty()).toBe(false);
78
- });
79
-
80
- it('should support multiple dependencies', () => {
81
- const a = chunk(2);
82
- const b = chunk(3);
83
- const c = chunk(4);
84
-
85
- const result = computed([a, b, c], (x, y, z) => x * y + z);
86
-
87
- expect(result.get()).toBe(10);
88
-
89
- b.set(5);
90
-
91
- expect(result.get()).toBe(14);
92
- });
93
- });
@@ -1,99 +0,0 @@
1
- import { chunk } from "../src/core/core";
2
- import { withHistory } from "../src/middleware/history";
3
-
4
-
5
- describe('Chunk with History', () => {
6
- it('should maintain history of changes', () => {
7
- const baseChunk = chunk(0);
8
- const historyChunk = withHistory(baseChunk);
9
-
10
- historyChunk.set(1);
11
- historyChunk.set(2);
12
- historyChunk.set(3);
13
-
14
- expect(historyChunk.getHistory()).toEqual([0, 1, 2, 3]);
15
- });
16
-
17
- it('should handle undo and redo operations', () => {
18
- const baseChunk = chunk(0);
19
- const historyChunk = withHistory(baseChunk);
20
- const callback = jest.fn();
21
-
22
- historyChunk.subscribe(callback);
23
- callback.mockClear(); // Clear initial subscription call
24
-
25
- historyChunk.set(1);
26
- historyChunk.set(2);
27
-
28
- expect(historyChunk.get()).toBe(2);
29
-
30
- historyChunk.undo();
31
- expect(historyChunk.get()).toBe(1);
32
-
33
- historyChunk.undo();
34
- expect(historyChunk.get()).toBe(0);
35
-
36
- historyChunk.redo();
37
- expect(historyChunk.get()).toBe(1);
38
-
39
- historyChunk.redo();
40
- expect(historyChunk.get()).toBe(2);
41
-
42
- expect(callback).toHaveBeenCalledTimes(6); // 2 sets + 2 undos + 2 redos
43
- });
44
-
45
- it('should handle branching history', () => {
46
- const baseChunk = chunk(0);
47
- const historyChunk = withHistory(baseChunk);
48
-
49
- historyChunk.set(1);
50
- historyChunk.set(2);
51
- historyChunk.undo();
52
- historyChunk.set(3); // This should create a new branch
53
-
54
- expect(historyChunk.getHistory()).toEqual([0, 1, 3]);
55
- expect(historyChunk.get()).toBe(3);
56
- });
57
-
58
- it('should respect maxHistory limit', () => {
59
- const baseChunk = chunk(0);
60
- const historyChunk = withHistory(baseChunk, { maxHistory: 3 });
61
-
62
- historyChunk.set(1);
63
- historyChunk.set(2);
64
- historyChunk.set(3);
65
- historyChunk.set(4);
66
-
67
- expect(historyChunk.getHistory()).toEqual([2, 3, 4]);
68
- });
69
-
70
- it('should handle canUndo and canRedo correctly', () => {
71
- const baseChunk = chunk(0);
72
- const historyChunk = withHistory(baseChunk);
73
-
74
- expect(historyChunk.canUndo()).toBe(false);
75
- expect(historyChunk.canRedo()).toBe(false);
76
-
77
- historyChunk.set(1);
78
- expect(historyChunk.canUndo()).toBe(true);
79
- expect(historyChunk.canRedo()).toBe(false);
80
-
81
- historyChunk.undo();
82
- expect(historyChunk.canUndo()).toBe(false);
83
- expect(historyChunk.canRedo()).toBe(true);
84
- });
85
-
86
- it('should clear history properly', () => {
87
- const baseChunk = chunk(0);
88
- const historyChunk = withHistory(baseChunk);
89
-
90
- historyChunk.set(1);
91
- historyChunk.set(2);
92
-
93
- historyChunk.clearHistory();
94
-
95
- expect(historyChunk.getHistory()).toEqual([2]);
96
- expect(historyChunk.canUndo()).toBe(false);
97
- expect(historyChunk.canRedo()).toBe(false);
98
- });
99
- });
@@ -1,37 +0,0 @@
1
- import { chunk } from "../src/core/core";
2
- import { logger } from "../src/middleware/logger";
3
- import { nonNegativeValidator } from "../src/middleware/validator";
4
-
5
- describe("Middleware Tests", () => {
6
- let consoleSpy: jest.SpyInstance;
7
-
8
- beforeEach(() => {
9
- consoleSpy = jest.spyOn(console, "log").mockImplementation();
10
- });
11
-
12
- afterEach(() => {
13
- jest.restoreAllMocks(); // Restores all spies
14
- jest.clearAllTimers(); // Clears any lingering timers
15
- });
16
-
17
- test("Logger middleware should log updates", () => {
18
- const count = chunk(0, [logger]);
19
-
20
- const unsubscribe = count.subscribe(() => { }); // Subscribe to capture updates
21
-
22
- try {
23
- count.set(5); // Should log: "Setting value: 5"
24
- expect(consoleSpy).toHaveBeenCalledWith("Setting value:", 5);
25
- } finally {
26
- unsubscribe(); // Ensure cleanup after test
27
- consoleSpy.mockRestore();
28
- }
29
- });
30
-
31
- test("Non-negative validator middleware should prevent negative values", () => {
32
- const count = chunk(0, [nonNegativeValidator]);
33
-
34
- expect(() => count.set(-5)).toThrow("Value must be non-negative!");
35
- expect(count.get()).toBe(0); // Value should remain unchanged
36
- });
37
- });
@@ -1,57 +0,0 @@
1
- /**
2
- * @jest-environment jsdom
3
- */
4
-
5
- import { chunk } from '../src/core/core';
6
- import { withPersistence } from "../src/middleware/persistence";
7
-
8
- describe('withPersistence', () => {
9
- beforeEach(() => {
10
- localStorage.clear();
11
- sessionStorage.clear();
12
- });
13
-
14
- it('should persist state to localStorage', () => {
15
- const baseChunk = chunk({ count: 0 });
16
- const persistedChunk = withPersistence(baseChunk, { key: 'test-key' });
17
-
18
- persistedChunk.set({ count: 1 });
19
- expect(JSON.parse(localStorage.getItem('test-key')!)).toEqual({ count: 1 });
20
- });
21
-
22
- it('should load persisted state on initialization', () => {
23
- localStorage.setItem('test-key', JSON.stringify({ count: 5 }));
24
-
25
- const baseChunk = chunk({ count: 0 });
26
- const persistedChunk = withPersistence(baseChunk, { key: 'test-key' });
27
-
28
- expect(persistedChunk.get()).toEqual({ count: 5 });
29
- });
30
-
31
- it('should use custom storage', () => {
32
- const mockStorage = {
33
- getItem: jest.fn(),
34
- setItem: jest.fn(),
35
- };
36
-
37
- const baseChunk = chunk({ count: 0 });
38
- withPersistence(baseChunk, {
39
- key: 'test-key',
40
- storage: mockStorage as unknown as Storage
41
- });
42
-
43
- expect(mockStorage.getItem).toHaveBeenCalledWith('test-key');
44
- });
45
-
46
- it('should use custom serializer/deserializer', () => {
47
- const baseChunk = chunk({ count: 0 });
48
- const persistedChunk = withPersistence(baseChunk, {
49
- key: 'test-key',
50
- serialize: value => btoa(JSON.stringify(value)),
51
- deserialize: value => JSON.parse(atob(value))
52
- });
53
-
54
- persistedChunk.set({ count: 1 });
55
- expect(localStorage.getItem('test-key')).toBe(btoa(JSON.stringify({ count: 1 })));
56
- });
57
- });
@@ -1,133 +0,0 @@
1
- import { chunk } from '../src/core/core';
2
- import { select } from '../src/core/selector'
3
-
4
- describe('select', () => {
5
- it('should create a selector that initially returns the correct value', () => {
6
- const source = chunk({ name: 'John', age: 25 });
7
- const nameSelector = select(source, user => user.name);
8
-
9
- expect(nameSelector.get()).toBe('John');
10
- });
11
-
12
- it('should update when selected value changes', () => {
13
- const source = chunk({ name: 'John', age: 25 });
14
- const nameSelector = select(source, user => user.name);
15
-
16
- source.set({ name: 'Jane', age: 25 });
17
- expect(nameSelector.get()).toBe('Jane');
18
- });
19
-
20
- it('should not notify subscribers when non-selected values change', () => {
21
- const source = chunk({ name: 'John', age: 25 });
22
- const nameSelector = select(source, user => user.name);
23
-
24
- const subscriber = jest.fn();
25
- nameSelector.subscribe(subscriber);
26
-
27
- // Reset the mock to ignore initial call
28
- subscriber.mockReset();
29
-
30
- // Update age only
31
- source.set({ name: 'John', age: 26 });
32
-
33
- expect(subscriber).not.toHaveBeenCalled();
34
- });
35
-
36
- it('should notify subscribers when selected value changes', () => {
37
- const source = chunk({ name: 'John', age: 25 });
38
- const nameSelector = select(source, user => user.name);
39
-
40
- const subscriber = jest.fn();
41
- nameSelector.subscribe(subscriber);
42
-
43
- // Reset the mock to ignore initial call
44
- subscriber.mockReset();
45
-
46
- source.set({ name: 'Jane', age: 25 });
47
-
48
- expect(subscriber).toHaveBeenCalledTimes(1);
49
- expect(subscriber).toHaveBeenCalledWith('Jane');
50
- });
51
-
52
- it('should prevent direct modifications to selector', () => {
53
- const source = chunk({ name: 'John', age: 25 });
54
- const nameSelector = select(source, user => user.name);
55
-
56
- expect(() => {
57
- nameSelector.set('Jane');
58
- }).toThrow('Cannot set values directly on a selector');
59
- });
60
-
61
- it('should work with complex selectors', () => {
62
- const source = chunk({ user: { profile: { name: 'John' } } });
63
- const nameSelector = select(source, state => state.user.profile.name);
64
-
65
- expect(nameSelector.get()).toBe('John');
66
-
67
- source.set({ user: { profile: { name: 'Jane' } } });
68
- expect(nameSelector.get()).toBe('Jane');
69
- });
70
-
71
- it('should handle array selectors', () => {
72
- const source = chunk({ items: [1, 2, 3] });
73
- const firstItemSelector = select(source, state => state.items[0]);
74
-
75
- expect(firstItemSelector.get()).toBe(1);
76
-
77
- source.set({ items: [4, 2, 3] });
78
- expect(firstItemSelector.get()).toBe(4);
79
- });
80
-
81
- it('should work with computed values', () => {
82
- const source = chunk({ numbers: [1, 2, 3, 4, 5] });
83
- const sumSelector = select(source, state =>
84
- state.numbers.reduce((sum, num) => sum + num, 0)
85
- );
86
-
87
- expect(sumSelector.get()).toBe(15);
88
-
89
- source.set({ numbers: [1, 2, 3] });
90
- expect(sumSelector.get()).toBe(6);
91
- });
92
-
93
- it('should properly clean up subscriptions on destroy', () => {
94
- const source = chunk({ name: 'John', age: 25 });
95
- const nameSelector = select(source, user => user.name);
96
-
97
- const subscriber = jest.fn();
98
- const unsubscribe = nameSelector.subscribe(subscriber);
99
-
100
- // Reset mock to ignore initial call
101
- subscriber.mockReset();
102
-
103
- unsubscribe();
104
- nameSelector.destroy();
105
- source.set({ name: 'Jane', age: 25 });
106
-
107
- expect(subscriber).not.toHaveBeenCalled();
108
- });
109
-
110
- it('should work with multiple independent selectors', () => {
111
- const source = chunk({ name: 'John', age: 25 });
112
- const nameSelector = select(source, user => user.name);
113
- const ageSelector = select(source, user => user.age);
114
-
115
- const nameSubscriber = jest.fn();
116
- const ageSubscriber = jest.fn();
117
-
118
- nameSelector.subscribe(nameSubscriber);
119
- ageSelector.subscribe(ageSubscriber);
120
-
121
- // Reset mocks to ignore initial calls
122
- nameSubscriber.mockReset();
123
- ageSubscriber.mockReset();
124
-
125
- source.set({ name: 'John', age: 26 });
126
- expect(nameSubscriber).not.toHaveBeenCalled();
127
- expect(ageSubscriber).toHaveBeenCalledWith(26);
128
-
129
- source.set({ name: 'Jane', age: 26 });
130
- expect(nameSubscriber).toHaveBeenCalledWith('Jane');
131
- expect(ageSubscriber).toHaveBeenCalledTimes(1); // Still from previous update
132
- });
133
- });
@@ -1,70 +0,0 @@
1
- import { chunk } from '../src/core/core';
2
-
3
- describe('chunk update', () => {
4
- it('should update value using updater function', () => {
5
- const store = chunk(5);
6
- store.set(value => value + 1);
7
- expect(store.get()).toBe(6);
8
- });
9
-
10
- it('should throw error if updater is not a function', () => {
11
- const store = chunk(5);
12
- // @ts-expect-error Testing invalid input
13
- expect(() => store.update('not a function')).toThrow('Updater must be a function');
14
- });
15
-
16
- it('should throw error if updater returns null or undefined', () => {
17
- const store = chunk(5);
18
- // @ts-expect-error Testing invalid input
19
- expect(() => store.update(() => null)).toThrow('Value cannot be null or undefined.');
20
- // @ts-expect-error Testing invalid input
21
- expect(() => store.update(() => undefined)).toThrow('Value cannot be null or undefined.');
22
- });
23
-
24
- it('should notify subscribers only if value changes', () => {
25
- const store = chunk(5);
26
- const subscriber = jest.fn();
27
- store.subscribe(subscriber);
28
-
29
- // Reset the mock to ignore initial subscription call
30
- subscriber.mockReset();
31
-
32
- // Update to same value
33
- store.set(value => value);
34
- expect(subscriber).not.toHaveBeenCalled();
35
-
36
- // Update to new value
37
- store.set(value => value + 1);
38
- expect(subscriber).toHaveBeenCalledWith(6);
39
- expect(subscriber).toHaveBeenCalledTimes(1);
40
- });
41
-
42
- it('should handle complex update logic', () => {
43
- const store = chunk(5);
44
- store.set(value => {
45
- if (value > 3) {
46
- return value * 2;
47
- }
48
- return value + 1;
49
- });
50
- expect(store.get()).toBe(10);
51
- });
52
-
53
- it('should maintain type safety', () => {
54
- interface User {
55
- name: string;
56
- age: number;
57
- }
58
-
59
- const store = chunk<User>({ name: 'John', age: 30 });
60
-
61
- store.set(user => ({
62
- ...user,
63
- age: user.age + 1
64
- }));
65
-
66
- const user = store.get();
67
- expect(user.age).toBe(31);
68
- expect(user.name).toBe('John');
69
- });
70
- });
package/tsconfig.json DELETED
@@ -1,23 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "moduleResolution": "Node",
6
- "outDir": "./dist",
7
- "declaration": true,
8
- "declarationDir": "./dist",
9
- "baseUrl": "./src",
10
- "paths": {
11
- "stunk/middleware": ["middleware"],
12
- "stunk/react": ["use-react"]
13
- },
14
- "typeRoots": ["./node_modules/@types", "./types"],
15
- "strict": true,
16
- "skipLibCheck": true,
17
- "jsx": "react",
18
- "esModuleInterop": true,
19
- "allowSyntheticDefaultImports": true
20
- },
21
- "include": ["src/**/*"],
22
- "exclude": ["node_modules", "dist"]
23
- }
File without changes