stunk 2.0.0 → 2.1.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.
- package/README.md +1 -1
- package/dist/core/asyncChunk.d.ts +4 -4
- package/dist/core/asyncChunk.js +2 -3
- package/dist/core/computed.js +36 -24
- package/dist/core/selector.js +3 -17
- package/dist/core/types.d.ts +3 -3
- package/dist/use-react/hooks/useAsyncChunk.d.ts +3 -3
- package/dist/use-react/hooks/useAsyncChunk.js +1 -1
- package/package.json +26 -14
- package/jest.config.js +0 -4
- package/src/core/asyncChunk.ts +0 -83
- package/src/core/computed.ts +0 -65
- package/src/core/core.ts +0 -128
- package/src/core/selector.ts +0 -27
- package/src/core/types.ts +0 -17
- package/src/index.ts +0 -10
- package/src/middleware/history.ts +0 -113
- package/src/middleware/index.ts +0 -7
- package/src/middleware/logger.ts +0 -6
- package/src/middleware/persistence.ts +0 -45
- package/src/middleware/validator.ts +0 -8
- package/src/use-react/hooks/useAsyncChunk.ts +0 -38
- package/src/use-react/hooks/useChunk.ts +0 -40
- package/src/use-react/hooks/useChunkProperty.ts +0 -21
- package/src/use-react/hooks/useChunkValue.ts +0 -15
- package/src/use-react/hooks/useChunkValues.ts +0 -35
- package/src/use-react/hooks/useComputed.ts +0 -34
- package/src/use-react/hooks/useDerive.ts +0 -15
- package/src/use-react/index.ts +0 -9
- package/src/utils.ts +0 -103
- package/tests/async-chunk.test.ts +0 -215
- package/tests/batch-chunk.test.ts +0 -108
- package/tests/chunk.test.ts +0 -189
- package/tests/computed.test.ts +0 -93
- package/tests/history.test.ts +0 -99
- package/tests/middleware.test.ts +0 -37
- package/tests/persist.test.ts +0 -57
- package/tests/select-chunk.test.ts +0 -133
- package/tests/update.test.ts +0 -70
- package/tsconfig.json +0 -23
package/tests/middleware.test.ts
DELETED
|
@@ -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
|
-
});
|
package/tests/persist.test.ts
DELETED
|
@@ -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
|
-
});
|
package/tests/update.test.ts
DELETED
|
@@ -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
|
-
}
|