react-broadcast-sync 1.5.1 → 1.8.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 (54) hide show
  1. package/README.md +59 -11
  2. package/dist/BroadcastProvider.d.ts +2 -7
  3. package/dist/index.cjs.js +1 -1
  4. package/dist/index.cjs.js.map +1 -1
  5. package/dist/index.esm.js +1 -1
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/types/types.d.ts +17 -0
  8. package/dist/utils/debug.d.ts +7 -1
  9. package/package.json +6 -1
  10. package/.eslintignore +0 -5
  11. package/.github/workflows/ci.yml +0 -61
  12. package/.github/workflows/release.yml +0 -39
  13. package/.husky/pre-commit +0 -5
  14. package/.nvmrc +0 -1
  15. package/.prettierignore +0 -4
  16. package/.prettierrc +0 -9
  17. package/.releaserc.json +0 -22
  18. package/CHANGELOG.md +0 -90
  19. package/assets/react-broadcast-sync-logo.png +0 -0
  20. package/commitlint.config.js +0 -3
  21. package/demo/react-broadcast-sync-demo/README.md +0 -130
  22. package/demo/react-broadcast-sync-demo/index.html +0 -14
  23. package/demo/react-broadcast-sync-demo/package-lock.json +0 -6379
  24. package/demo/react-broadcast-sync-demo/package.json +0 -42
  25. package/demo/react-broadcast-sync-demo/public/assets/react-broadcast-sync-demo-video.gif +0 -0
  26. package/demo/react-broadcast-sync-demo/public/assets/react-broadcast-sync-logo.png +0 -0
  27. package/demo/react-broadcast-sync-demo/src/App.tsx +0 -277
  28. package/demo/react-broadcast-sync-demo/src/components/Counter.tsx +0 -63
  29. package/demo/react-broadcast-sync-demo/src/components/TextSync.tsx +0 -61
  30. package/demo/react-broadcast-sync-demo/src/components/TodoList.tsx +0 -186
  31. package/demo/react-broadcast-sync-demo/src/hooks/useCounterBroadcast.ts +0 -96
  32. package/demo/react-broadcast-sync-demo/src/hooks/useTextBroadcast.ts +0 -37
  33. package/demo/react-broadcast-sync-demo/src/hooks/useTodoBroadcast.ts +0 -249
  34. package/demo/react-broadcast-sync-demo/src/main.tsx +0 -11
  35. package/demo/react-broadcast-sync-demo/src/theme.ts +0 -62
  36. package/demo/react-broadcast-sync-demo/tsconfig.json +0 -26
  37. package/demo/react-broadcast-sync-demo/tsconfig.node.json +0 -10
  38. package/demo/react-broadcast-sync-demo/vite.config.ts +0 -12
  39. package/eslint.config.js +0 -38
  40. package/jest.config.cjs +0 -9
  41. package/jest.setup.ts +0 -1
  42. package/rollup.config.mjs +0 -98
  43. package/src/BroadcastProvider.tsx +0 -80
  44. package/src/hooks/useBroadcastChannel.ts +0 -464
  45. package/src/index.ts +0 -9
  46. package/src/tests/BroadcastProvider.test.tsx +0 -163
  47. package/src/tests/debug.test.ts +0 -239
  48. package/src/tests/messageUtils.test.ts +0 -252
  49. package/src/tests/useBroadcastChannel.same-tab.test.tsx +0 -992
  50. package/src/tests/useBroadcastChannel.test.tsx +0 -1243
  51. package/src/types/types.ts +0 -124
  52. package/src/utils/debug.ts +0 -54
  53. package/src/utils/messageUtils.ts +0 -122
  54. package/tsconfig.json +0 -36
@@ -1,163 +0,0 @@
1
- import { render, screen, act } from '@testing-library/react';
2
- import '@testing-library/jest-dom';
3
- import { BroadcastProvider, useBroadcastProvider } from '../BroadcastProvider';
4
-
5
- // Mock BroadcastChannel
6
- let mockChannels: any[] = [];
7
-
8
- class MockBroadcastChannel {
9
- name: string;
10
- onmessage: ((event: MessageEvent) => void) | null = null;
11
- postMessage = jest.fn();
12
- close = jest.fn();
13
- addEventListener = jest.fn((event: string, callback: (event: MessageEvent) => void) => {
14
- if (event === 'message') {
15
- this.onmessage = callback;
16
- }
17
- });
18
- removeEventListener = jest.fn();
19
-
20
- constructor(name: string) {
21
- this.name = name;
22
- mockChannels.push(this);
23
- }
24
-
25
- simulateMessage(data: any) {
26
- if (this.onmessage) {
27
- this.onmessage({ data } as MessageEvent);
28
- }
29
- }
30
- }
31
-
32
- // Test component that uses the provider
33
- const TestComponent = () => {
34
- const { messages, error } = useBroadcastProvider();
35
- return (
36
- <div>
37
- <div data-testid="error">{error}</div>
38
- <div data-testid="messages">
39
- {messages.map(msg => (
40
- <div key={msg.id} data-testid={`message-${msg.id}`}>
41
- {msg.message.text}
42
- </div>
43
- ))}
44
- </div>
45
- </div>
46
- );
47
- };
48
-
49
- describe('BroadcastProvider', () => {
50
- beforeEach(() => {
51
- mockChannels = [];
52
- global.BroadcastChannel = MockBroadcastChannel as any;
53
- });
54
-
55
- afterEach(() => {
56
- jest.clearAllMocks();
57
- });
58
-
59
- it('creates a broadcast channel with the provided name', () => {
60
- render(
61
- <BroadcastProvider channelName="test-channel">
62
- <TestComponent />
63
- </BroadcastProvider>
64
- );
65
-
66
- // The provider creates two channels - one in the provider and one in useBroadcastChannel
67
- expect(mockChannels).toHaveLength(2);
68
- expect(mockChannels[0].name).toBe('test-channel');
69
- expect(mockChannels[1].name).toBe('test-channel');
70
- });
71
-
72
- it('provides context values to children', () => {
73
- render(
74
- <BroadcastProvider channelName="test-channel">
75
- <TestComponent />
76
- </BroadcastProvider>
77
- );
78
-
79
- expect(screen.getByTestId('error')).toHaveTextContent('');
80
- expect(screen.getByTestId('messages')).toBeEmptyDOMElement();
81
- });
82
-
83
- it('handles messages through the provider', () => {
84
- render(
85
- <BroadcastProvider channelName="test-channel">
86
- <TestComponent />
87
- </BroadcastProvider>
88
- );
89
-
90
- const message = {
91
- id: 'test-1',
92
- type: 'test',
93
- message: { text: 'Hello' },
94
- source: 'other-tab',
95
- timestamp: Date.now(),
96
- };
97
-
98
- act(() => {
99
- mockChannels[0].simulateMessage(message);
100
- });
101
-
102
- expect(screen.getByTestId('message-test-1')).toHaveTextContent('Hello');
103
- });
104
-
105
- it('handles errors when BroadcastChannel is not supported', () => {
106
- const originalBroadcastChannel = global.BroadcastChannel;
107
- delete (global as any).BroadcastChannel;
108
-
109
- render(
110
- <BroadcastProvider channelName="test-channel">
111
- <TestComponent />
112
- </BroadcastProvider>
113
- );
114
-
115
- expect(screen.getByTestId('error')).toHaveTextContent(
116
- 'BroadcastChannel is not supported in this browser. Please check browser compatibility.'
117
- );
118
-
119
- // Restore BroadcastChannel for other tests
120
- global.BroadcastChannel = originalBroadcastChannel;
121
- });
122
-
123
- it('cleans up channel on unmount', () => {
124
- const { unmount } = render(
125
- <BroadcastProvider channelName="test-channel">
126
- <TestComponent />
127
- </BroadcastProvider>
128
- );
129
-
130
- unmount();
131
-
132
- expect(mockChannels[0].close).toHaveBeenCalled();
133
- });
134
-
135
- it('handles error boundary when child component throws', () => {
136
- const ThrowComponent = () => {
137
- throw new Error('Test error');
138
- };
139
-
140
- const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
141
-
142
- render(
143
- <BroadcastProvider channelName="test-channel">
144
- <ThrowComponent />
145
- </BroadcastProvider>
146
- );
147
-
148
- expect(screen.getByText('Error occurred in BroadcastProvider')).toBeInTheDocument();
149
- expect(consoleError).toHaveBeenCalledWith('BroadcastChannel error:', expect.any(Error));
150
-
151
- consoleError.mockRestore();
152
- });
153
-
154
- it('throws error when useBroadcastProvider is used outside provider', () => {
155
- const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
156
-
157
- expect(() => {
158
- render(<TestComponent />);
159
- }).toThrow('useBroadcastProvider must be used within a BroadcastProvider');
160
-
161
- consoleError.mockRestore();
162
- });
163
- });
@@ -1,239 +0,0 @@
1
- // Set debug mode before requiring the debug module
2
- process.env.REACT_APP_DEBUG_BROADCAST = 'true';
3
- import { debug } from '../utils/debug';
4
-
5
- describe('debug', () => {
6
- const originalEnv = process.env;
7
- const originalConsole = {
8
- log: console.log,
9
- warn: console.warn,
10
- error: console.error,
11
- };
12
-
13
- beforeEach(() => {
14
- process.env = { ...originalEnv };
15
- console.log = jest.fn();
16
- console.warn = jest.fn();
17
- console.error = jest.fn();
18
- });
19
-
20
- afterEach(() => {
21
- console.log = originalConsole.log;
22
- console.warn = originalConsole.warn;
23
- console.error = originalConsole.error;
24
- process.env = originalEnv;
25
- });
26
-
27
- describe('when debug mode is disabled', () => {
28
- beforeEach(() => {
29
- process.env.REACT_APP_DEBUG_BROADCAST = 'false';
30
- });
31
-
32
- it('does not log anything', () => {
33
- debug.channel.created('test-channel');
34
- debug.message.sent({ id: '123' });
35
- debug.error('test error');
36
-
37
- expect(console.log).not.toHaveBeenCalled();
38
- expect(console.warn).not.toHaveBeenCalled();
39
- expect(console.error).not.toHaveBeenCalled();
40
- });
41
- });
42
-
43
- describe('when debug mode is enabled', () => {
44
- beforeEach(() => {
45
- process.env.REACT_APP_DEBUG_BROADCAST = 'true';
46
- });
47
-
48
- it('logs channel events', () => {
49
- debug.channel.created('test-channel');
50
- expect(console.log).toHaveBeenCalledWith(
51
- expect.stringMatching(
52
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
53
- ),
54
- 'Channel created:',
55
- 'test-channel'
56
- );
57
-
58
- debug.channel.closed('test-channel');
59
- expect(console.log).toHaveBeenCalledWith(
60
- expect.stringMatching(
61
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
62
- ),
63
- 'Channel closed:',
64
- 'test-channel'
65
- );
66
- });
67
-
68
- it('logs message events', () => {
69
- const testMessage = { id: '123', type: 'test' };
70
-
71
- debug.message.sent(testMessage);
72
- expect(console.log).toHaveBeenCalledWith(
73
- expect.stringMatching(
74
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
75
- ),
76
- 'Message sent:',
77
- testMessage
78
- );
79
-
80
- debug.message.received(testMessage);
81
- expect(console.log).toHaveBeenCalledWith(
82
- expect.stringMatching(
83
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
84
- ),
85
- 'Message received:',
86
- testMessage
87
- );
88
-
89
- debug.message.cleared('123');
90
- expect(console.log).toHaveBeenCalledWith(
91
- expect.stringMatching(
92
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
93
- ),
94
- 'Message cleared:',
95
- '123'
96
- );
97
-
98
- debug.message.expired('123');
99
- expect(console.log).toHaveBeenCalledWith(
100
- expect.stringMatching(
101
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
102
- ),
103
- 'Message expired:',
104
- '123'
105
- );
106
-
107
- debug.message.duplicate('123');
108
- expect(console.warn).toHaveBeenCalledWith(
109
- expect.stringMatching(
110
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
111
- ),
112
- 'Duplicate message ignored:',
113
- '123'
114
- );
115
- });
116
-
117
- it('logs cleanup events', () => {
118
- debug.cleanup.started();
119
- expect(console.log).toHaveBeenCalledWith(
120
- expect.stringMatching(
121
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
122
- ),
123
- 'Cleanup started',
124
- ''
125
- );
126
-
127
- debug.cleanup.completed(5);
128
- expect(console.log).toHaveBeenCalledWith(
129
- expect.stringMatching(
130
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
131
- ),
132
- 'Cleanup completed:',
133
- { removedCount: 5 }
134
- );
135
- });
136
-
137
- it('logs errors', () => {
138
- debug.error('test error');
139
- expect(console.error).toHaveBeenCalledWith(
140
- expect.stringMatching(
141
- /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
142
- ),
143
- 'Error:',
144
- 'test error'
145
- );
146
- });
147
-
148
- it('logs info with no data', () => {
149
- debug.channel.created('test-channel');
150
- expect(console.log).toHaveBeenCalledWith(
151
- expect.stringMatching(/\[.*react-broadcast-sync\]/),
152
- 'Channel created:',
153
- 'test-channel'
154
- );
155
- });
156
-
157
- it('logs warn with no data', () => {
158
- debug.message.duplicate('123');
159
- expect(console.warn).toHaveBeenCalledWith(
160
- expect.stringMatching(/\[.*react-broadcast-sync\]/),
161
- 'Duplicate message ignored:',
162
- '123'
163
- );
164
- });
165
-
166
- it('logs error with no data', () => {
167
- debug.error('error!');
168
- expect(console.error).toHaveBeenCalledWith(
169
- expect.stringMatching(/\[.*react-broadcast-sync\]/),
170
- 'Error:',
171
- 'error!'
172
- );
173
- });
174
-
175
- it('logs message with no data', () => {
176
- debug.cleanup.started();
177
- expect(console.log).toHaveBeenCalledWith(
178
- expect.stringContaining('[react-broadcast-sync]'),
179
- 'Cleanup started',
180
- ''
181
- );
182
- });
183
- });
184
-
185
- describe('when process.env is undefined', () => {
186
- beforeEach(() => {
187
- // @ts-expect-error - Testing invalid input
188
- process.env = undefined;
189
- });
190
-
191
- it('does not log anything', () => {
192
- debug.channel.created('test-channel');
193
- expect(console.log).not.toHaveBeenCalled();
194
- });
195
- });
196
-
197
- it('logs info messages', () => {
198
- debug.channel.created('test-channel');
199
- expect(console.log).toHaveBeenCalledWith(
200
- expect.stringContaining('[react-broadcast-sync]'),
201
- 'Channel created:',
202
- 'test-channel'
203
- );
204
- });
205
-
206
- it('logs warning messages', () => {
207
- debug.message.duplicate('test-id');
208
- expect(console.warn).toHaveBeenCalledWith(
209
- expect.stringContaining('[react-broadcast-sync]'),
210
- 'Duplicate message ignored:',
211
- 'test-id'
212
- );
213
- });
214
-
215
- it('logs error messages', () => {
216
- debug.error('Test error');
217
- expect(console.error).toHaveBeenCalledWith(
218
- expect.stringContaining('[react-broadcast-sync]'),
219
- 'Error:',
220
- 'Test error'
221
- );
222
- });
223
-
224
- it('logs cleanup information', () => {
225
- debug.cleanup.started();
226
- expect(console.log).toHaveBeenCalledWith(
227
- expect.stringContaining('[react-broadcast-sync]'),
228
- 'Cleanup started',
229
- ''
230
- );
231
-
232
- debug.cleanup.completed(2);
233
- expect(console.log).toHaveBeenCalledWith(
234
- expect.stringContaining('[react-broadcast-sync]'),
235
- 'Cleanup completed:',
236
- { removedCount: 2 }
237
- );
238
- });
239
- });
@@ -1,252 +0,0 @@
1
- import {
2
- generateRandomPart,
3
- generateSourceName,
4
- generateMessageId,
5
- isValidMessage,
6
- isMessageExpired,
7
- createMessage,
8
- getInternalMessageType,
9
- isInternalType,
10
- isValidInternalClearMessage,
11
- debounce,
12
- } from '../utils/messageUtils';
13
-
14
- beforeEach(() => {
15
- jest.useFakeTimers();
16
- });
17
- afterEach(() => {
18
- jest.useRealTimers();
19
- });
20
-
21
- describe('messageUtils', () => {
22
- it('generateRandomPart returns a non-empty string', () => {
23
- const part = generateRandomPart();
24
- expect(typeof part).toBe('string');
25
- expect(part).toHaveLength(9);
26
- });
27
-
28
- it('generateSourceName includes "tab-" prefix', () => {
29
- expect(generateSourceName()).toMatch(/^tab-/);
30
- });
31
-
32
- it('generateMessageId returns a base64 string', () => {
33
- const id = generateMessageId('source-1', Date.now());
34
- expect(typeof id).toBe('string');
35
- expect(id).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
36
- });
37
-
38
- it('isValidMessage identifies a valid message object', () => {
39
- expect(isValidMessage({ id: '123' })).toBe(true);
40
- expect(isValidMessage(null)).toBe(false);
41
- expect(isValidMessage({})).toBe(false);
42
- });
43
-
44
- it('isMessageExpired works correctly', () => {
45
- const now = Date.now();
46
- expect(isMessageExpired({ expirationDate: now - 1000 })).toBe(true);
47
- expect(isMessageExpired({ expirationDate: now + 1000 })).toBe(false);
48
- expect(isMessageExpired({})).toBe(false);
49
- });
50
-
51
- it('createMessage returns a valid message object', () => {
52
- const source = 'tab-abc';
53
- const msg = createMessage('type1', { hello: 'world' }, source, { expirationDuration: 1000 });
54
- expect(msg).toHaveProperty('id');
55
- expect(msg).toHaveProperty('type', 'type1');
56
- expect(msg).toHaveProperty('message.hello', 'world');
57
- expect(msg).toHaveProperty('source', source);
58
- expect(msg.expirationDate).toBeGreaterThan(Date.now());
59
- });
60
-
61
- it('createMessage uses provided expirationDate when given', () => {
62
- const expirationDate = Date.now() + 5000;
63
- const message = createMessage('type', {}, 'source', { expirationDate });
64
- expect(message.expirationDate).toBe(expirationDate);
65
- });
66
-
67
- it('getInternalMessageType produces unique hashes for different channels', () => {
68
- const one = getInternalMessageType('CLEAR_SENT_MESSAGES', 'chanA');
69
- const two = getInternalMessageType('CLEAR_SENT_MESSAGES', 'chanB');
70
- expect(one).not.toBe(two);
71
- });
72
-
73
- it('getInternalMessageType generates consistent hash string', () => {
74
- const type = getInternalMessageType('CLEAR_SENT_MESSAGES', 'my-channel', 'ns');
75
- expect(type).toContain('__INTERNAL__');
76
- expect(type).toContain('CLEAR_SENT_MESSAGES');
77
- });
78
-
79
- it('isInternalType detects internal message types', () => {
80
- const type = getInternalMessageType('CLEAR_SENT_MESSAGES', 'chan');
81
- expect(isInternalType(type)).toBe(true);
82
- expect(isInternalType('normal-type')).toBe(false);
83
- });
84
-
85
- it('isValidInternalClearMessage detects valid structure', () => {
86
- const valid = {
87
- id: 'abc',
88
- type: getInternalMessageType('CLEAR_SENT_MESSAGES', 'ch'),
89
- source: 'tab-1',
90
- };
91
- const invalid = { id: 123, type: 'bad', source: 'tab-1' };
92
- expect(isValidInternalClearMessage(valid)).toBe(true);
93
- expect(isValidInternalClearMessage(invalid)).toBe(false);
94
- });
95
- });
96
-
97
- describe('debounce', () => {
98
- it('debounce delays function call and can cancel/flush', () => {
99
- const fn = jest.fn();
100
- const debounced = debounce(fn, 100);
101
-
102
- debounced();
103
- expect(fn).not.toHaveBeenCalled();
104
-
105
- debounced.flush();
106
- expect(fn).toHaveBeenCalled();
107
-
108
- debounced();
109
- debounced.cancel();
110
- jest.advanceTimersByTime(150);
111
- expect(fn).toHaveBeenCalledTimes(1);
112
- });
113
-
114
- it('debounce handles multiple calls and preserves this context', () => {
115
- const obj = {
116
- value: 0,
117
- increment: function () {
118
- this.value++;
119
- },
120
- };
121
- const debounced = debounce(obj.increment.bind(obj), 100);
122
-
123
- debounced();
124
- debounced();
125
- debounced.flush();
126
- expect(obj.value).toBe(1);
127
- });
128
-
129
- it('debounce flush returns undefined when no pending call', () => {
130
- const fn = jest.fn();
131
- const debounced = debounce(fn, 100);
132
-
133
- expect(debounced.flush()).toBeUndefined();
134
- expect(fn).not.toHaveBeenCalled();
135
- });
136
-
137
- it('debounce handles function with return value', () => {
138
- const fn = jest.fn().mockReturnValue('test');
139
- const debounced = debounce(fn, 100);
140
-
141
- debounced();
142
- expect(debounced.flush()).toBe('test');
143
- });
144
-
145
- it('debounce.cancel cancels the timeout', () => {
146
- const fn = jest.fn();
147
- const debounced = debounce(fn, 100);
148
- debounced();
149
- debounced.cancel();
150
- jest.advanceTimersByTime(200);
151
- expect(fn).not.toHaveBeenCalled();
152
- });
153
-
154
- it('debounce.flush executes immediately', () => {
155
- const fn = jest.fn();
156
- const debounced = debounce(fn, 100);
157
- debounced('test');
158
- debounced.flush();
159
- expect(fn).toHaveBeenCalledWith('test');
160
- });
161
-
162
- it('debounce handles multiple calls with different arguments', () => {
163
- const fn = jest.fn();
164
- const debounced = debounce(fn, 100);
165
-
166
- debounced('first');
167
- debounced('second');
168
- debounced('third');
169
-
170
- expect(fn).not.toHaveBeenCalled();
171
- debounced.flush();
172
- expect(fn).toHaveBeenCalledTimes(1);
173
- expect(fn).toHaveBeenCalledWith('third');
174
- });
175
-
176
- it('debounce handles cancel and flush in sequence', () => {
177
- const fn = jest.fn();
178
- const debounced = debounce(fn, 100);
179
-
180
- debounced('test');
181
- debounced.cancel();
182
- expect(debounced.flush()).toBeUndefined();
183
- expect(fn).not.toHaveBeenCalled();
184
- });
185
-
186
- it('debounce.flush executes the function with latest arguments immediately', () => {
187
- jest.useFakeTimers();
188
-
189
- const fn = jest.fn((x: number) => x + 1);
190
- const debounced = debounce(fn, 200);
191
-
192
- debounced(41);
193
-
194
- expect(fn).not.toHaveBeenCalled();
195
-
196
- const flushResult = debounced.flush();
197
-
198
- expect(fn).toHaveBeenCalledTimes(1);
199
- expect(fn).toHaveBeenCalledWith(41);
200
- expect(flushResult).toBe(42);
201
-
202
- jest.useRealTimers();
203
- });
204
-
205
- it('debounce.flush does nothing if timeout is set but lastArgs were reset manually', () => {
206
- const fn = jest.fn();
207
-
208
- const debounced = debounce(fn, 100);
209
-
210
- // Call once, schedule the debounce
211
- debounced('first');
212
-
213
- // Simulate internal cleanup (cannot access lastArgs directly, so instead cancel it)
214
- debounced.cancel(); // This will clear lastArgs and lastThis but not call fn
215
-
216
- const result = debounced.flush(); // Should do nothing
217
-
218
- expect(fn).not.toHaveBeenCalled();
219
- expect(result).toBeUndefined();
220
- });
221
-
222
- it('debounce.flush handles case where timeoutId exists but lastArgs is null', () => {
223
- const fn = jest.fn();
224
- const debounced = debounce(fn, 100);
225
-
226
- // Schedule a call
227
- debounced('test');
228
-
229
- // Force the timeout to be set but clear lastArgs
230
- jest.advanceTimersByTime(50); // Advance partially through the timeout
231
- debounced.cancel(); // This clears lastArgs but might not clear timeoutId immediately
232
-
233
- // Call flush when timeoutId exists but lastArgs is null
234
- const result = debounced.flush();
235
- expect(result).toBeUndefined();
236
- expect(fn).not.toHaveBeenCalled();
237
- });
238
-
239
- it('debounce.flush handles multiple calls with different arguments and preserves return value', () => {
240
- const fn = jest.fn((x: number) => x * 2);
241
- const debounced = debounce(fn, 100);
242
-
243
- debounced(1);
244
- debounced(2);
245
- debounced(3);
246
-
247
- const result = debounced.flush();
248
- expect(result).toBe(6); // 3 * 2
249
- expect(fn).toHaveBeenCalledTimes(1);
250
- expect(fn).toHaveBeenCalledWith(3);
251
- });
252
- });