react-broadcast-sync 1.0.0 → 1.1.1
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/.eslintignore +5 -0
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/release.yml +39 -0
- package/.husky/pre-commit +0 -1
- package/.nvmrc +1 -0
- package/.prettierignore +1 -0
- package/.prettierrc +5 -1
- package/CHANGELOG.md +27 -0
- package/README.md +118 -47
- package/demo/react-broadcast-sync-demo/README.md +130 -0
- package/demo/react-broadcast-sync-demo/index.html +14 -0
- package/demo/react-broadcast-sync-demo/package-lock.json +6379 -0
- package/demo/react-broadcast-sync-demo/package.json +42 -0
- package/demo/react-broadcast-sync-demo/public/assets/react-broadcast-sync-logo.png +0 -0
- package/demo/react-broadcast-sync-demo/src/App.tsx +277 -0
- package/demo/react-broadcast-sync-demo/src/components/Counter.tsx +63 -0
- package/demo/react-broadcast-sync-demo/src/components/TextSync.tsx +61 -0
- package/demo/react-broadcast-sync-demo/src/components/TodoList.tsx +186 -0
- package/demo/react-broadcast-sync-demo/src/hooks/useCounterBroadcast.ts +96 -0
- package/demo/react-broadcast-sync-demo/src/hooks/useTextBroadcast.ts +37 -0
- package/demo/react-broadcast-sync-demo/src/hooks/useTodoBroadcast.ts +249 -0
- package/demo/react-broadcast-sync-demo/src/main.tsx +11 -0
- package/demo/react-broadcast-sync-demo/src/theme.ts +62 -0
- package/demo/react-broadcast-sync-demo/tsconfig.json +26 -0
- package/demo/react-broadcast-sync-demo/tsconfig.node.json +10 -0
- package/demo/react-broadcast-sync-demo/vite.config.ts +12 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/eslint.config.js +14 -5
- package/jest.config.cjs +8 -9
- package/package.json +63 -33
- package/rollup.config.mjs +27 -21
- package/src/BroadcastProvider.tsx +6 -6
- package/src/hooks/useBroadcastChannel.ts +122 -95
- package/src/index.ts +1 -1
- package/src/tests/BroadcastProvider.test.tsx +7 -7
- package/src/tests/debug.test.ts +65 -25
- package/src/tests/messageUtils.test.ts +20 -15
- package/src/tests/useBroadcastChannel.test.tsx +48 -33
- package/src/types/types.ts +19 -23
- package/src/utils/debug.ts +1 -1
- package/src/utils/messageUtils.ts +19 -24
- package/dist/useBroadcastChannel.d.ts +0 -3
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import {
|
|
3
|
+
BroadcastActions,
|
|
4
|
+
BroadcastMessage,
|
|
5
|
+
BroadcastOptions,
|
|
6
|
+
SendMessageOptions,
|
|
7
|
+
} from '../types/types';
|
|
8
|
+
import {
|
|
9
|
+
generateSourceName,
|
|
10
|
+
isValidMessage,
|
|
11
|
+
isMessageExpired,
|
|
7
12
|
createMessage,
|
|
8
13
|
getInternalMessageType,
|
|
9
14
|
isInternalType,
|
|
@@ -13,9 +18,9 @@ import {
|
|
|
13
18
|
import { debug } from '../utils/debug';
|
|
14
19
|
|
|
15
20
|
const INTERNAL_MESSAGE_TYPES = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
CLEAR_MESSAGE: 'CLEAR_MESSAGE',
|
|
22
|
+
CLEAR_ALL_MESSAGES: 'CLEAR_ALL_MESSAGES',
|
|
23
|
+
} as const;
|
|
19
24
|
|
|
20
25
|
export const useBroadcastChannel = (
|
|
21
26
|
channelName: string,
|
|
@@ -25,10 +30,10 @@ export const useBroadcastChannel = (
|
|
|
25
30
|
sourceName,
|
|
26
31
|
cleaningInterval = 1000,
|
|
27
32
|
keepLatestMessage = false,
|
|
28
|
-
registeredTypes = [],
|
|
33
|
+
registeredTypes = [],
|
|
29
34
|
namespace = '',
|
|
30
35
|
deduplicationTTL = 5 * 60 * 1000,
|
|
31
|
-
cleanupDebounceMs = 0
|
|
36
|
+
cleanupDebounceMs = 0,
|
|
32
37
|
} = options;
|
|
33
38
|
|
|
34
39
|
// State
|
|
@@ -41,8 +46,16 @@ export const useBroadcastChannel = (
|
|
|
41
46
|
const receivedMessageIds = useRef(new Map<string, number>());
|
|
42
47
|
const source = useRef(sourceName || generateSourceName()).current;
|
|
43
48
|
const internalTypes = useRef({
|
|
44
|
-
CLEAR_MESSAGE: getInternalMessageType(
|
|
45
|
-
|
|
49
|
+
CLEAR_MESSAGE: getInternalMessageType(
|
|
50
|
+
INTERNAL_MESSAGE_TYPES.CLEAR_MESSAGE,
|
|
51
|
+
channelName,
|
|
52
|
+
namespace
|
|
53
|
+
),
|
|
54
|
+
CLEAR_ALL_MESSAGES: getInternalMessageType(
|
|
55
|
+
INTERNAL_MESSAGE_TYPES.CLEAR_ALL_MESSAGES,
|
|
56
|
+
channelName,
|
|
57
|
+
namespace
|
|
58
|
+
),
|
|
46
59
|
}).current;
|
|
47
60
|
|
|
48
61
|
const resolvedChannelName = useMemo(() => {
|
|
@@ -67,107 +80,121 @@ export const useBroadcastChannel = (
|
|
|
67
80
|
setTimeout(() => setError(null), 3000);
|
|
68
81
|
}, []);
|
|
69
82
|
|
|
70
|
-
const clearMessage = useCallback(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
const clearMessage = useCallback(
|
|
84
|
+
(id: string) => {
|
|
85
|
+
setMessages(prev => prev.filter(msg => msg.id !== id));
|
|
86
|
+
setSentMessages(prev => prev.filter(msg => msg.id !== id));
|
|
87
|
+
channel.current?.postMessage({
|
|
88
|
+
id,
|
|
89
|
+
type: internalTypes.CLEAR_MESSAGE,
|
|
90
|
+
source,
|
|
91
|
+
});
|
|
92
|
+
debug.message.cleared(id);
|
|
93
|
+
},
|
|
94
|
+
[source, internalTypes.CLEAR_MESSAGE]
|
|
95
|
+
);
|
|
80
96
|
|
|
81
97
|
const clearAllMessages = useCallback(() => {
|
|
82
98
|
setMessages([]);
|
|
83
99
|
setSentMessages([]);
|
|
84
|
-
channel.current?.postMessage({
|
|
85
|
-
id: internalTypes.CLEAR_ALL_MESSAGES,
|
|
86
|
-
type: internalTypes.CLEAR_ALL_MESSAGES,
|
|
87
|
-
source
|
|
100
|
+
channel.current?.postMessage({
|
|
101
|
+
id: internalTypes.CLEAR_ALL_MESSAGES,
|
|
102
|
+
type: internalTypes.CLEAR_ALL_MESSAGES,
|
|
103
|
+
source,
|
|
88
104
|
});
|
|
89
105
|
}, [source, internalTypes.CLEAR_ALL_MESSAGES]);
|
|
90
106
|
|
|
91
|
-
const clearSentMessage = useCallback(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
messageContent: any,
|
|
103
|
-
options: SendMessageOptions = {}
|
|
104
|
-
) => {
|
|
105
|
-
const channelCurrent = channel.current;
|
|
106
|
-
if (!channelCurrent) {
|
|
107
|
-
const error = 'Channel not available';
|
|
108
|
-
debug.error(error);
|
|
109
|
-
setErrorMessage(error);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
107
|
+
const clearSentMessage = useCallback(
|
|
108
|
+
(id: string) => {
|
|
109
|
+
setSentMessages(prev => prev.filter(msg => msg.id !== id));
|
|
110
|
+
channel.current?.postMessage({
|
|
111
|
+
id,
|
|
112
|
+
type: internalTypes.CLEAR_MESSAGE,
|
|
113
|
+
source,
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
[source, internalTypes.CLEAR_MESSAGE]
|
|
117
|
+
);
|
|
112
118
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
channelCurrent.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
debug.error(error);
|
|
121
|
-
setErrorMessage(error);
|
|
122
|
-
}
|
|
123
|
-
}, [source, setErrorMessage]);
|
|
124
|
-
|
|
125
|
-
const handleMessage = useCallback((event: MessageEvent) => {
|
|
126
|
-
try {
|
|
127
|
-
const message: BroadcastMessage = event.data;
|
|
128
|
-
debug.message.received(message);
|
|
129
|
-
|
|
130
|
-
if (!isValidMessage(message)) return;
|
|
131
|
-
if (message.source === source) return;
|
|
132
|
-
if (!isInternalType(message.type) && registeredTypes.length > 0 && !registeredTypes.includes(message.type)) return;
|
|
133
|
-
if (isMessageExpired(message)) {
|
|
134
|
-
debug.message.expired(message.id);
|
|
119
|
+
const postMessage = useCallback(
|
|
120
|
+
(messageType: string, messageContent: any, options: SendMessageOptions = {}) => {
|
|
121
|
+
const channelCurrent = channel.current;
|
|
122
|
+
if (!channelCurrent) {
|
|
123
|
+
const error = 'Channel not available';
|
|
124
|
+
debug.error(error);
|
|
125
|
+
setErrorMessage(error);
|
|
135
126
|
return;
|
|
136
127
|
}
|
|
137
128
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
129
|
+
try {
|
|
130
|
+
const message = createMessage(messageType, messageContent, source, options);
|
|
131
|
+
channelCurrent.postMessage(message);
|
|
132
|
+
debug.message.sent(message);
|
|
133
|
+
setSentMessages(prev => [...prev, message]);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
const error = 'Failed to send message';
|
|
136
|
+
debug.error(error);
|
|
137
|
+
setErrorMessage(error);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
[source, setErrorMessage]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const handleMessage = useCallback(
|
|
144
|
+
(event: MessageEvent) => {
|
|
145
|
+
try {
|
|
146
|
+
const message: BroadcastMessage = event.data;
|
|
147
|
+
debug.message.received(message);
|
|
148
|
+
|
|
149
|
+
if (!isValidMessage(message)) return;
|
|
150
|
+
if (message.source === source) return;
|
|
151
|
+
if (
|
|
152
|
+
!isInternalType(message.type) &&
|
|
153
|
+
registeredTypes.length > 0 &&
|
|
154
|
+
!registeredTypes.includes(message.type)
|
|
155
|
+
)
|
|
156
|
+
return;
|
|
157
|
+
if (isMessageExpired(message)) {
|
|
158
|
+
debug.message.expired(message.id);
|
|
143
159
|
return;
|
|
144
160
|
}
|
|
145
|
-
|
|
146
|
-
if (message
|
|
147
|
-
|
|
161
|
+
|
|
162
|
+
if (isValidInternalClearMessage(message)) {
|
|
163
|
+
if (message.type === internalTypes.CLEAR_MESSAGE) {
|
|
164
|
+
setMessages(prev =>
|
|
165
|
+
prev.filter(msg => !(msg.id === message.id && msg.source === message.source))
|
|
166
|
+
);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (message.type === internalTypes.CLEAR_ALL_MESSAGES) {
|
|
171
|
+
setMessages(prev => prev.filter(msg => msg.source !== message.source));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
const receivedAt = receivedMessageIds.current.get(message.id);
|
|
178
|
+
if (receivedAt && now - receivedAt < deduplicationTTL) {
|
|
179
|
+
debug.message.duplicate(message.id);
|
|
148
180
|
return;
|
|
149
181
|
}
|
|
150
|
-
}
|
|
151
182
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
183
|
+
receivedMessageIds.current.set(message.id, now);
|
|
184
|
+
setMessages(prev => (keepLatestMessage ? [message] : [...prev, message]));
|
|
185
|
+
} catch (e) {
|
|
186
|
+
const error = 'Error processing broadcast message';
|
|
187
|
+
debug.error(error);
|
|
188
|
+
setErrorMessage(error);
|
|
157
189
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
} catch (e) {
|
|
162
|
-
const error = 'Error processing broadcast message';
|
|
163
|
-
debug.error(error);
|
|
164
|
-
setErrorMessage(error);
|
|
165
|
-
}
|
|
166
|
-
}, [source, registeredTypes, keepLatestMessage, setErrorMessage, internalTypes, deduplicationTTL]);
|
|
190
|
+
},
|
|
191
|
+
[source, registeredTypes, keepLatestMessage, setErrorMessage, internalTypes, deduplicationTTL]
|
|
192
|
+
);
|
|
167
193
|
|
|
168
194
|
useEffect(() => {
|
|
169
195
|
if (typeof BroadcastChannel === 'undefined') {
|
|
170
|
-
const error =
|
|
196
|
+
const error =
|
|
197
|
+
'BroadcastChannel is not supported in this browser. Please check browser compatibility.';
|
|
171
198
|
debug.error(error);
|
|
172
199
|
setErrorMessage(error);
|
|
173
200
|
return;
|
|
@@ -213,7 +240,7 @@ export const useBroadcastChannel = (
|
|
|
213
240
|
}
|
|
214
241
|
}
|
|
215
242
|
}, 60 * 1000);
|
|
216
|
-
|
|
243
|
+
|
|
217
244
|
return () => clearInterval(interval);
|
|
218
245
|
}, [deduplicationTTL]);
|
|
219
246
|
|
|
@@ -229,4 +256,4 @@ export const useBroadcastChannel = (
|
|
|
229
256
|
};
|
|
230
257
|
};
|
|
231
258
|
|
|
232
|
-
export default useBroadcastChannel;
|
|
259
|
+
export default useBroadcastChannel;
|
package/src/index.ts
CHANGED
|
@@ -36,7 +36,7 @@ const TestComponent = () => {
|
|
|
36
36
|
<div>
|
|
37
37
|
<div data-testid="error">{error}</div>
|
|
38
38
|
<div data-testid="messages">
|
|
39
|
-
{messages.map(
|
|
39
|
+
{messages.map(msg => (
|
|
40
40
|
<div key={msg.id} data-testid={`message-${msg.id}`}>
|
|
41
41
|
{msg.message.text}
|
|
42
42
|
</div>
|
|
@@ -92,7 +92,7 @@ describe('BroadcastProvider', () => {
|
|
|
92
92
|
type: 'test',
|
|
93
93
|
message: { text: 'Hello' },
|
|
94
94
|
source: 'other-tab',
|
|
95
|
-
timestamp: Date.now()
|
|
95
|
+
timestamp: Date.now(),
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
act(() => {
|
|
@@ -138,7 +138,7 @@ describe('BroadcastProvider', () => {
|
|
|
138
138
|
};
|
|
139
139
|
|
|
140
140
|
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
render(
|
|
143
143
|
<BroadcastProvider channelName="test-channel">
|
|
144
144
|
<ThrowComponent />
|
|
@@ -147,17 +147,17 @@ describe('BroadcastProvider', () => {
|
|
|
147
147
|
|
|
148
148
|
expect(screen.getByText('Error occurred in BroadcastProvider')).toBeInTheDocument();
|
|
149
149
|
expect(consoleError).toHaveBeenCalledWith('BroadcastChannel error:', expect.any(Error));
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
consoleError.mockRestore();
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
it('throws error when useBroadcastProvider is used outside provider', () => {
|
|
155
155
|
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
expect(() => {
|
|
158
158
|
render(<TestComponent />);
|
|
159
159
|
}).toThrow('useBroadcastProvider must be used within a BroadcastProvider');
|
|
160
|
-
|
|
160
|
+
|
|
161
161
|
consoleError.mockRestore();
|
|
162
162
|
});
|
|
163
|
-
});
|
|
163
|
+
});
|
package/src/tests/debug.test.ts
CHANGED
|
@@ -48,14 +48,18 @@ describe('debug', () => {
|
|
|
48
48
|
it('logs channel events', () => {
|
|
49
49
|
debug.channel.created('test-channel');
|
|
50
50
|
expect(console.log).toHaveBeenCalledWith(
|
|
51
|
-
expect.stringMatching(
|
|
51
|
+
expect.stringMatching(
|
|
52
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
53
|
+
),
|
|
52
54
|
'Channel created:',
|
|
53
55
|
'test-channel'
|
|
54
56
|
);
|
|
55
57
|
|
|
56
58
|
debug.channel.closed('test-channel');
|
|
57
59
|
expect(console.log).toHaveBeenCalledWith(
|
|
58
|
-
expect.stringMatching(
|
|
60
|
+
expect.stringMatching(
|
|
61
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
62
|
+
),
|
|
59
63
|
'Channel closed:',
|
|
60
64
|
'test-channel'
|
|
61
65
|
);
|
|
@@ -63,38 +67,48 @@ describe('debug', () => {
|
|
|
63
67
|
|
|
64
68
|
it('logs message events', () => {
|
|
65
69
|
const testMessage = { id: '123', type: 'test' };
|
|
66
|
-
|
|
70
|
+
|
|
67
71
|
debug.message.sent(testMessage);
|
|
68
72
|
expect(console.log).toHaveBeenCalledWith(
|
|
69
|
-
expect.stringMatching(
|
|
73
|
+
expect.stringMatching(
|
|
74
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
75
|
+
),
|
|
70
76
|
'Message sent:',
|
|
71
77
|
testMessage
|
|
72
78
|
);
|
|
73
79
|
|
|
74
80
|
debug.message.received(testMessage);
|
|
75
81
|
expect(console.log).toHaveBeenCalledWith(
|
|
76
|
-
expect.stringMatching(
|
|
82
|
+
expect.stringMatching(
|
|
83
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
84
|
+
),
|
|
77
85
|
'Message received:',
|
|
78
86
|
testMessage
|
|
79
87
|
);
|
|
80
88
|
|
|
81
89
|
debug.message.cleared('123');
|
|
82
90
|
expect(console.log).toHaveBeenCalledWith(
|
|
83
|
-
expect.stringMatching(
|
|
91
|
+
expect.stringMatching(
|
|
92
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
93
|
+
),
|
|
84
94
|
'Message cleared:',
|
|
85
95
|
'123'
|
|
86
96
|
);
|
|
87
97
|
|
|
88
98
|
debug.message.expired('123');
|
|
89
99
|
expect(console.log).toHaveBeenCalledWith(
|
|
90
|
-
expect.stringMatching(
|
|
100
|
+
expect.stringMatching(
|
|
101
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
102
|
+
),
|
|
91
103
|
'Message expired:',
|
|
92
104
|
'123'
|
|
93
105
|
);
|
|
94
106
|
|
|
95
107
|
debug.message.duplicate('123');
|
|
96
108
|
expect(console.warn).toHaveBeenCalledWith(
|
|
97
|
-
expect.stringMatching(
|
|
109
|
+
expect.stringMatching(
|
|
110
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
111
|
+
),
|
|
98
112
|
'Duplicate message ignored:',
|
|
99
113
|
'123'
|
|
100
114
|
);
|
|
@@ -103,14 +117,18 @@ describe('debug', () => {
|
|
|
103
117
|
it('logs cleanup events', () => {
|
|
104
118
|
debug.cleanup.started();
|
|
105
119
|
expect(console.log).toHaveBeenCalledWith(
|
|
106
|
-
expect.stringMatching(
|
|
120
|
+
expect.stringMatching(
|
|
121
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
122
|
+
),
|
|
107
123
|
'Cleanup started',
|
|
108
124
|
''
|
|
109
125
|
);
|
|
110
126
|
|
|
111
127
|
debug.cleanup.completed(5);
|
|
112
128
|
expect(console.log).toHaveBeenCalledWith(
|
|
113
|
-
expect.stringMatching(
|
|
129
|
+
expect.stringMatching(
|
|
130
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
131
|
+
),
|
|
114
132
|
'Cleanup completed:',
|
|
115
133
|
{ removedCount: 5 }
|
|
116
134
|
);
|
|
@@ -119,7 +137,9 @@ describe('debug', () => {
|
|
|
119
137
|
it('logs errors', () => {
|
|
120
138
|
debug.error('test error');
|
|
121
139
|
expect(console.error).toHaveBeenCalledWith(
|
|
122
|
-
expect.stringMatching(
|
|
140
|
+
expect.stringMatching(
|
|
141
|
+
/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[react-broadcast-sync\]/
|
|
142
|
+
),
|
|
123
143
|
'Error:',
|
|
124
144
|
'test error'
|
|
125
145
|
);
|
|
@@ -153,18 +173,18 @@ describe('debug', () => {
|
|
|
153
173
|
});
|
|
154
174
|
|
|
155
175
|
it('logs message with no data', () => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
176
|
+
debug.cleanup.started();
|
|
177
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
178
|
+
expect.stringContaining('[react-broadcast-sync]'),
|
|
179
|
+
'Cleanup started',
|
|
180
|
+
''
|
|
181
|
+
);
|
|
182
|
+
});
|
|
163
183
|
});
|
|
164
184
|
|
|
165
185
|
describe('when process.env is undefined', () => {
|
|
166
186
|
beforeEach(() => {
|
|
167
|
-
// @ts-
|
|
187
|
+
// @ts-expect-error - Testing invalid input
|
|
168
188
|
process.env = undefined;
|
|
169
189
|
});
|
|
170
190
|
|
|
@@ -176,24 +196,44 @@ describe('debug', () => {
|
|
|
176
196
|
|
|
177
197
|
it('logs info messages', () => {
|
|
178
198
|
debug.channel.created('test-channel');
|
|
179
|
-
expect(console.log).toHaveBeenCalledWith(
|
|
199
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
200
|
+
expect.stringContaining('[react-broadcast-sync]'),
|
|
201
|
+
'Channel created:',
|
|
202
|
+
'test-channel'
|
|
203
|
+
);
|
|
180
204
|
});
|
|
181
205
|
|
|
182
206
|
it('logs warning messages', () => {
|
|
183
207
|
debug.message.duplicate('test-id');
|
|
184
|
-
expect(console.warn).toHaveBeenCalledWith(
|
|
208
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
209
|
+
expect.stringContaining('[react-broadcast-sync]'),
|
|
210
|
+
'Duplicate message ignored:',
|
|
211
|
+
'test-id'
|
|
212
|
+
);
|
|
185
213
|
});
|
|
186
214
|
|
|
187
215
|
it('logs error messages', () => {
|
|
188
216
|
debug.error('Test error');
|
|
189
|
-
expect(console.error).toHaveBeenCalledWith(
|
|
217
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
218
|
+
expect.stringContaining('[react-broadcast-sync]'),
|
|
219
|
+
'Error:',
|
|
220
|
+
'Test error'
|
|
221
|
+
);
|
|
190
222
|
});
|
|
191
223
|
|
|
192
224
|
it('logs cleanup information', () => {
|
|
193
225
|
debug.cleanup.started();
|
|
194
|
-
expect(console.log).toHaveBeenCalledWith(
|
|
226
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
227
|
+
expect.stringContaining('[react-broadcast-sync]'),
|
|
228
|
+
'Cleanup started',
|
|
229
|
+
''
|
|
230
|
+
);
|
|
195
231
|
|
|
196
232
|
debug.cleanup.completed(2);
|
|
197
|
-
expect(console.log).toHaveBeenCalledWith(
|
|
233
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
234
|
+
expect.stringContaining('[react-broadcast-sync]'),
|
|
235
|
+
'Cleanup completed:',
|
|
236
|
+
{ removedCount: 2 }
|
|
237
|
+
);
|
|
198
238
|
});
|
|
199
|
-
});
|
|
239
|
+
});
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getInternalMessageType,
|
|
9
9
|
isInternalType,
|
|
10
10
|
isValidInternalClearMessage,
|
|
11
|
-
debounce
|
|
11
|
+
debounce,
|
|
12
12
|
} from '../utils/messageUtils';
|
|
13
13
|
|
|
14
14
|
beforeEach(() => {
|
|
@@ -86,7 +86,7 @@ describe('messageUtils', () => {
|
|
|
86
86
|
const valid = {
|
|
87
87
|
id: 'abc',
|
|
88
88
|
type: getInternalMessageType('CLEAR_ALL_MESSAGES', 'ch'),
|
|
89
|
-
source: 'tab-1'
|
|
89
|
+
source: 'tab-1',
|
|
90
90
|
};
|
|
91
91
|
const invalid = { id: 123, type: 'bad', source: 'tab-1' };
|
|
92
92
|
expect(isValidInternalClearMessage(valid)).toBe(true);
|
|
@@ -112,9 +112,14 @@ describe('debounce', () => {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
it('debounce handles multiple calls and preserves this context', () => {
|
|
115
|
-
const obj = {
|
|
115
|
+
const obj = {
|
|
116
|
+
value: 0,
|
|
117
|
+
increment: function () {
|
|
118
|
+
this.value++;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
116
121
|
const debounced = debounce(obj.increment.bind(obj), 100);
|
|
117
|
-
|
|
122
|
+
|
|
118
123
|
debounced();
|
|
119
124
|
debounced();
|
|
120
125
|
debounced.flush();
|
|
@@ -124,7 +129,7 @@ describe('debounce', () => {
|
|
|
124
129
|
it('debounce flush returns undefined when no pending call', () => {
|
|
125
130
|
const fn = jest.fn();
|
|
126
131
|
const debounced = debounce(fn, 100);
|
|
127
|
-
|
|
132
|
+
|
|
128
133
|
expect(debounced.flush()).toBeUndefined();
|
|
129
134
|
expect(fn).not.toHaveBeenCalled();
|
|
130
135
|
});
|
|
@@ -132,7 +137,7 @@ describe('debounce', () => {
|
|
|
132
137
|
it('debounce handles function with return value', () => {
|
|
133
138
|
const fn = jest.fn().mockReturnValue('test');
|
|
134
139
|
const debounced = debounce(fn, 100);
|
|
135
|
-
|
|
140
|
+
|
|
136
141
|
debounced();
|
|
137
142
|
expect(debounced.flush()).toBe('test');
|
|
138
143
|
});
|
|
@@ -145,7 +150,7 @@ describe('debounce', () => {
|
|
|
145
150
|
jest.advanceTimersByTime(200);
|
|
146
151
|
expect(fn).not.toHaveBeenCalled();
|
|
147
152
|
});
|
|
148
|
-
|
|
153
|
+
|
|
149
154
|
it('debounce.flush executes immediately', () => {
|
|
150
155
|
const fn = jest.fn();
|
|
151
156
|
const debounced = debounce(fn, 100);
|
|
@@ -157,11 +162,11 @@ describe('debounce', () => {
|
|
|
157
162
|
it('debounce handles multiple calls with different arguments', () => {
|
|
158
163
|
const fn = jest.fn();
|
|
159
164
|
const debounced = debounce(fn, 100);
|
|
160
|
-
|
|
165
|
+
|
|
161
166
|
debounced('first');
|
|
162
167
|
debounced('second');
|
|
163
168
|
debounced('third');
|
|
164
|
-
|
|
169
|
+
|
|
165
170
|
expect(fn).not.toHaveBeenCalled();
|
|
166
171
|
debounced.flush();
|
|
167
172
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
@@ -171,7 +176,7 @@ describe('debounce', () => {
|
|
|
171
176
|
it('debounce handles cancel and flush in sequence', () => {
|
|
172
177
|
const fn = jest.fn();
|
|
173
178
|
const debounced = debounce(fn, 100);
|
|
174
|
-
|
|
179
|
+
|
|
175
180
|
debounced('test');
|
|
176
181
|
debounced.cancel();
|
|
177
182
|
expect(debounced.flush()).toBeUndefined();
|
|
@@ -217,14 +222,14 @@ describe('debounce', () => {
|
|
|
217
222
|
it('debounce.flush handles case where timeoutId exists but lastArgs is null', () => {
|
|
218
223
|
const fn = jest.fn();
|
|
219
224
|
const debounced = debounce(fn, 100);
|
|
220
|
-
|
|
225
|
+
|
|
221
226
|
// Schedule a call
|
|
222
227
|
debounced('test');
|
|
223
|
-
|
|
228
|
+
|
|
224
229
|
// Force the timeout to be set but clear lastArgs
|
|
225
230
|
jest.advanceTimersByTime(50); // Advance partially through the timeout
|
|
226
231
|
debounced.cancel(); // This clears lastArgs but might not clear timeoutId immediately
|
|
227
|
-
|
|
232
|
+
|
|
228
233
|
// Call flush when timeoutId exists but lastArgs is null
|
|
229
234
|
const result = debounced.flush();
|
|
230
235
|
expect(result).toBeUndefined();
|
|
@@ -234,11 +239,11 @@ describe('debounce', () => {
|
|
|
234
239
|
it('debounce.flush handles multiple calls with different arguments and preserves return value', () => {
|
|
235
240
|
const fn = jest.fn((x: number) => x * 2);
|
|
236
241
|
const debounced = debounce(fn, 100);
|
|
237
|
-
|
|
242
|
+
|
|
238
243
|
debounced(1);
|
|
239
244
|
debounced(2);
|
|
240
245
|
debounced(3);
|
|
241
|
-
|
|
246
|
+
|
|
242
247
|
const result = debounced.flush();
|
|
243
248
|
expect(result).toBe(6); // 3 * 2
|
|
244
249
|
expect(fn).toHaveBeenCalledTimes(1);
|