@alwatr/signal 5.2.0 → 5.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.
- package/CHANGELOG.md +29 -0
- package/README.md +12 -12
- package/dist/core/computed-signal.d.ts +6 -6
- package/dist/core/computed-signal.d.ts.map +1 -1
- package/dist/core/effect-signal.d.ts +2 -2
- package/dist/core/effect-signal.d.ts.map +1 -1
- package/dist/core/state-signal.d.ts +4 -4
- package/dist/core/state-signal.d.ts.map +1 -1
- package/dist/creators/computed.d.ts +2 -2
- package/dist/creators/effect.d.ts +1 -1
- package/dist/creators/state.d.ts +2 -2
- package/dist/main.cjs +2 -621
- package/dist/main.cjs.map +2 -2
- package/dist/main.mjs +2 -586
- package/dist/main.mjs.map +2 -2
- package/dist/operators/debounce.d.ts +2 -2
- package/dist/operators/filter.d.ts +2 -2
- package/dist/operators/filter.d.ts.map +1 -1
- package/dist/operators/map.d.ts +2 -2
- package/dist/type.d.ts +3 -3
- package/dist/type.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/core/computed-signal.test.js +22 -6
- package/src/core/state-signal.test.js +21 -6
- package/src/operators/debounce.test.js +206 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {describe, beforeEach, afterEach, it, expect, jest} from '@jest/globals';
|
|
2
|
+
import {ComputedSignal, createDebouncedSignal, StateSignal} from '@alwatr/signal';
|
|
3
|
+
|
|
4
|
+
describe('createDebouncedSignal', () => {
|
|
5
|
+
/** @type {ComputedSignal<number>} */
|
|
6
|
+
let debouncedSignal;
|
|
7
|
+
/** @type {StateSignal<number>} */
|
|
8
|
+
let sourceSignal;
|
|
9
|
+
const signalId = 'test-debounce-signal';
|
|
10
|
+
/**
|
|
11
|
+
* @type {import("jest-mock").Mock<import("jest-mock").UnknownFunction>}
|
|
12
|
+
*/
|
|
13
|
+
let mockFunc;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockFunc = jest.fn();
|
|
17
|
+
jest.useFakeTimers();
|
|
18
|
+
sourceSignal = new StateSignal({signalId, initialValue: 0});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
jest.clearAllTimers();
|
|
23
|
+
jest.useRealTimers();
|
|
24
|
+
sourceSignal?.destroy();
|
|
25
|
+
debouncedSignal?.destroy();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should create a debounced signal with default config', () => {
|
|
29
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
30
|
+
expect(debouncedSignal.get()).toBe(0);
|
|
31
|
+
expect(debouncedSignal.signalId).toBe(`${signalId}-debounced`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should debounce updates with trailing edge', async () => {
|
|
35
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
36
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
37
|
+
sourceSignal.set(1);
|
|
38
|
+
sourceSignal.set(2);
|
|
39
|
+
expect(debouncedSignal.get()).toBe(0);
|
|
40
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
41
|
+
expect(debouncedSignal.get()).toBe(2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should support leading edge', async () => {
|
|
45
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100, leading: true});
|
|
46
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
47
|
+
sourceSignal.set(1);
|
|
48
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
49
|
+
expect(debouncedSignal.get()).toBe(1);
|
|
50
|
+
sourceSignal.set(2);
|
|
51
|
+
await jest.advanceTimersByTimeAsync(10);
|
|
52
|
+
expect(debouncedSignal.get()).toBe(1);
|
|
53
|
+
await jest.advanceTimersByTimeAsync(100);
|
|
54
|
+
expect(debouncedSignal.get()).toBe(2);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should support trailing edge', async () => {
|
|
58
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100, trailing: true});
|
|
59
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
60
|
+
sourceSignal.set(1);
|
|
61
|
+
sourceSignal.set(2);
|
|
62
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
63
|
+
expect(debouncedSignal.get()).toBe(0);
|
|
64
|
+
await jest.advanceTimersByTimeAsync(100);
|
|
65
|
+
expect(debouncedSignal.get()).toBe(2);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should cancel debounced updates on destroy', async () => {
|
|
69
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
70
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
71
|
+
sourceSignal.set(1);
|
|
72
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
73
|
+
debouncedSignal.destroy();
|
|
74
|
+
expect(() => debouncedSignal.get()).toThrow(); // Should throw on access after
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should call onDestroy callback if provided', () => {
|
|
78
|
+
const onDestroyMock = jest.fn();
|
|
79
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100, onDestroy: onDestroyMock});
|
|
80
|
+
debouncedSignal.destroy();
|
|
81
|
+
expect(onDestroyMock).toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should use custom signalId if provided', () => {
|
|
85
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100, signalId: 'custom-debounced'});
|
|
86
|
+
expect(debouncedSignal.signalId).toBe('custom-debounced');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle multiple rapid updates correctly', async () => {
|
|
90
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
91
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
92
|
+
sourceSignal.set(1);
|
|
93
|
+
await jest.advanceTimersByTimeAsync(50);
|
|
94
|
+
sourceSignal.set(2);
|
|
95
|
+
await jest.advanceTimersByTimeAsync(50);
|
|
96
|
+
sourceSignal.set(3);
|
|
97
|
+
expect(debouncedSignal.get()).toBe(0);
|
|
98
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
99
|
+
expect(debouncedSignal.get()).toBe(3);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should notify subscriber with receivePrevious', async () => {
|
|
103
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
104
|
+
const callback = jest.fn();
|
|
105
|
+
debouncedSignal.subscribe(callback);
|
|
106
|
+
await jest.advanceTimersByTimeAsync(1);
|
|
107
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
108
|
+
expect(callback).toHaveBeenCalledWith(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should notify subscribers when debounced value changes', async () => {
|
|
112
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
113
|
+
const callback = jest.fn();
|
|
114
|
+
debouncedSignal.subscribe(callback, {receivePrevious: false});
|
|
115
|
+
sourceSignal.set(1);
|
|
116
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
117
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
118
|
+
expect(callback).toHaveBeenCalledWith(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should not notify if debounced value does not change', async () => {
|
|
122
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
123
|
+
const callback = jest.fn();
|
|
124
|
+
debouncedSignal.subscribe(callback, {receivePrevious: false});
|
|
125
|
+
sourceSignal.set(0); // Same as initial
|
|
126
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
127
|
+
expect(callback).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should notify multiple subscribers', async () => {
|
|
131
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
132
|
+
const callback1 = jest.fn();
|
|
133
|
+
const callback2 = jest.fn();
|
|
134
|
+
debouncedSignal.subscribe(callback1, {receivePrevious: false});
|
|
135
|
+
debouncedSignal.subscribe(callback2, {receivePrevious: false});
|
|
136
|
+
sourceSignal.set(5);
|
|
137
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
138
|
+
expect(callback1).toHaveBeenCalledTimes(1);
|
|
139
|
+
expect(callback1).toHaveBeenCalledWith(5);
|
|
140
|
+
expect(callback2).toHaveBeenCalledTimes(1);
|
|
141
|
+
expect(callback2).toHaveBeenCalledWith(5);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should not notify unsubscribed listeners', async () => {
|
|
145
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
146
|
+
const callback = jest.fn();
|
|
147
|
+
const subscription = debouncedSignal.subscribe(callback, {receivePrevious: false});
|
|
148
|
+
subscription.unsubscribe();
|
|
149
|
+
sourceSignal.set(10);
|
|
150
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
151
|
+
expect(callback).not.toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle subscriptions with the "once" option', async () => {
|
|
155
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
156
|
+
const callback = jest.fn();
|
|
157
|
+
debouncedSignal.subscribe(callback, {once: true, receivePrevious: false});
|
|
158
|
+
sourceSignal.set(10);
|
|
159
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
160
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
161
|
+
expect(callback).toHaveBeenCalledWith(10);
|
|
162
|
+
sourceSignal.set(20);
|
|
163
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
164
|
+
expect(callback).toHaveBeenCalledTimes(1); // Should not be called again
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should resolve untilNext() with the next debounced value', async () => {
|
|
168
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
169
|
+
const untilNextPromise = debouncedSignal.untilNext();
|
|
170
|
+
sourceSignal.set(5);
|
|
171
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
172
|
+
await expect(untilNextPromise).resolves.toBe(5);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should continue notifying other subscribers if one callback throws an error', async () => {
|
|
176
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
177
|
+
const callback1 = jest.fn(() => {
|
|
178
|
+
throw new Error('Test error');
|
|
179
|
+
});
|
|
180
|
+
const callback2 = jest.fn();
|
|
181
|
+
debouncedSignal.subscribe(callback1, {receivePrevious: false});
|
|
182
|
+
debouncedSignal.subscribe(callback2, {receivePrevious: false});
|
|
183
|
+
sourceSignal.set(10);
|
|
184
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
185
|
+
expect(callback1).toHaveBeenCalledTimes(1);
|
|
186
|
+
expect(callback2).toHaveBeenCalledTimes(1);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should update without any subscribers', async () => {
|
|
190
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
191
|
+
expect(debouncedSignal.get()).toBe(0);
|
|
192
|
+
sourceSignal.set(7);
|
|
193
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
194
|
+
expect(debouncedSignal.get()).toBe(7);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should not notify after destroy', async () => {
|
|
198
|
+
debouncedSignal = createDebouncedSignal(sourceSignal, {delay: 100});
|
|
199
|
+
const callback = jest.fn();
|
|
200
|
+
debouncedSignal.subscribe(callback, {receivePrevious: false});
|
|
201
|
+
debouncedSignal.destroy();
|
|
202
|
+
sourceSignal.set(10);
|
|
203
|
+
await jest.advanceTimersByTimeAsync(110);
|
|
204
|
+
expect(callback).not.toHaveBeenCalled();
|
|
205
|
+
});
|
|
206
|
+
});
|