@remember-web/sse 0.1.0-alpha.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/README.md +238 -0
- package/dist/src/core/BaseSseClient.cjs.js +97 -0
- package/dist/src/core/BaseSseClient.cjs.js.map +1 -0
- package/dist/src/core/BaseSseClient.d.ts +33 -0
- package/dist/src/core/BaseSseClient.d.ts.map +1 -0
- package/dist/src/core/BaseSseClient.esm.js +89 -0
- package/dist/src/core/BaseSseClient.esm.js.map +1 -0
- package/dist/src/core/SseClient.cjs.js +382 -0
- package/dist/src/core/SseClient.cjs.js.map +1 -0
- package/dist/src/core/SseClient.d.ts +24 -0
- package/dist/src/core/SseClient.d.ts.map +1 -0
- package/dist/src/core/SseClient.esm.js +370 -0
- package/dist/src/core/SseClient.esm.js.map +1 -0
- package/dist/src/core/backoff.cjs.js +30 -0
- package/dist/src/core/backoff.cjs.js.map +1 -0
- package/dist/src/core/backoff.d.ts +12 -0
- package/dist/src/core/backoff.d.ts.map +1 -0
- package/dist/src/core/backoff.esm.js +28 -0
- package/dist/src/core/backoff.esm.js.map +1 -0
- package/dist/src/core/emitter.cjs.js +57 -0
- package/dist/src/core/emitter.cjs.js.map +1 -0
- package/dist/src/core/emitter.d.ts +14 -0
- package/dist/src/core/emitter.d.ts.map +1 -0
- package/dist/src/core/emitter.esm.js +51 -0
- package/dist/src/core/emitter.esm.js.map +1 -0
- package/dist/src/core/eventNames.cjs.js +64 -0
- package/dist/src/core/eventNames.cjs.js.map +1 -0
- package/dist/src/core/eventNames.d.ts +31 -0
- package/dist/src/core/eventNames.d.ts.map +1 -0
- package/dist/src/core/eventNames.esm.js +54 -0
- package/dist/src/core/eventNames.esm.js.map +1 -0
- package/dist/src/core/retryWait.cjs.js +20 -0
- package/dist/src/core/retryWait.cjs.js.map +1 -0
- package/dist/src/core/retryWait.d.ts +6 -0
- package/dist/src/core/retryWait.d.ts.map +1 -0
- package/dist/src/core/retryWait.esm.js +18 -0
- package/dist/src/core/retryWait.esm.js.map +1 -0
- package/dist/src/core/statusMachine.cjs.js +56 -0
- package/dist/src/core/statusMachine.cjs.js.map +1 -0
- package/dist/src/core/statusMachine.d.ts +9 -0
- package/dist/src/core/statusMachine.d.ts.map +1 -0
- package/dist/src/core/statusMachine.esm.js +54 -0
- package/dist/src/core/statusMachine.esm.js.map +1 -0
- package/dist/src/core/stream.cjs.js +136 -0
- package/dist/src/core/stream.cjs.js.map +1 -0
- package/dist/src/core/stream.d.ts +42 -0
- package/dist/src/core/stream.d.ts.map +1 -0
- package/dist/src/core/stream.esm.js +128 -0
- package/dist/src/core/stream.esm.js.map +1 -0
- package/dist/src/core/ticket.cjs.js +237 -0
- package/dist/src/core/ticket.cjs.js.map +1 -0
- package/dist/src/core/ticket.d.ts +31 -0
- package/dist/src/core/ticket.d.ts.map +1 -0
- package/dist/src/core/ticket.esm.js +220 -0
- package/dist/src/core/ticket.esm.js.map +1 -0
- package/dist/src/core/types.cjs.js +54 -0
- package/dist/src/core/types.cjs.js.map +1 -0
- package/dist/src/core/types.d.ts +131 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.esm.js +50 -0
- package/dist/src/core/types.esm.js.map +1 -0
- package/dist/src/core/wire.cjs.js +29 -0
- package/dist/src/core/wire.cjs.js.map +1 -0
- package/dist/src/core/wire.d.ts +7 -0
- package/dist/src/core/wire.d.ts.map +1 -0
- package/dist/src/core/wire.esm.js +23 -0
- package/dist/src/core/wire.esm.js.map +1 -0
- package/dist/src/createSseClient.cjs.js +30 -0
- package/dist/src/createSseClient.cjs.js.map +1 -0
- package/dist/src/createSseClient.d.ts +8 -0
- package/dist/src/createSseClient.d.ts.map +1 -0
- package/dist/src/createSseClient.esm.js +28 -0
- package/dist/src/createSseClient.esm.js.map +1 -0
- package/dist/src/index.cjs.js +10 -0
- package/dist/src/index.cjs.js.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.esm.js +3 -0
- package/dist/src/index.esm.js.map +1 -0
- package/dist/src/react/createSseContext.cjs.js +130 -0
- package/dist/src/react/createSseContext.cjs.js.map +1 -0
- package/dist/src/react/createSseContext.d.ts +32 -0
- package/dist/src/react/createSseContext.d.ts.map +1 -0
- package/dist/src/react/createSseContext.esm.js +122 -0
- package/dist/src/react/createSseContext.esm.js.map +1 -0
- package/dist/src/react/index.cjs.js +8 -0
- package/dist/src/react/index.cjs.js.map +1 -0
- package/dist/src/react/index.d.ts +3 -0
- package/dist/src/react/index.d.ts.map +1 -0
- package/dist/src/react/index.esm.js +2 -0
- package/dist/src/react/index.esm.js.map +1 -0
- package/dist/src/testing/doubles.d.ts +50 -0
- package/dist/src/testing/doubles.d.ts.map +1 -0
- package/dist/src/testing/harness.d.ts +43 -0
- package/dist/src/testing/harness.d.ts.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/worker/WorkerSseClient.cjs.js +240 -0
- package/dist/src/worker/WorkerSseClient.cjs.js.map +1 -0
- package/dist/src/worker/WorkerSseClient.d.ts +23 -0
- package/dist/src/worker/WorkerSseClient.d.ts.map +1 -0
- package/dist/src/worker/WorkerSseClient.esm.js +228 -0
- package/dist/src/worker/WorkerSseClient.esm.js.map +1 -0
- package/dist/src/worker/hub.d.ts +19 -0
- package/dist/src/worker/hub.d.ts.map +1 -0
- package/dist/src/worker/portRegistry.d.ts +41 -0
- package/dist/src/worker/portRegistry.d.ts.map +1 -0
- package/dist/src/worker/protocol.d.ts +63 -0
- package/dist/src/worker/protocol.d.ts.map +1 -0
- package/dist/src/worker/sse.worker.d.ts +2 -0
- package/dist/src/worker/sse.worker.d.ts.map +1 -0
- package/dist/src/worker/ticketDelegate.d.ts +16 -0
- package/dist/src/worker/ticketDelegate.d.ts.map +1 -0
- package/dist/src/worker/workerConnection.d.ts +28 -0
- package/dist/src/worker/workerConnection.d.ts.map +1 -0
- package/dist/worker/sse.worker.js +1865 -0
- package/dist/worker/sse.worker.js.map +1 -0
- package/package.json +87 -0
- package/src/core/BaseSseClient.test.ts +142 -0
- package/src/core/BaseSseClient.ts +103 -0
- package/src/core/SseClient.connection.test.ts +863 -0
- package/src/core/SseClient.scenarios.test.ts +164 -0
- package/src/core/SseClient.test.ts +63 -0
- package/src/core/SseClient.ts +310 -0
- package/src/core/backoff.test.ts +43 -0
- package/src/core/backoff.ts +39 -0
- package/src/core/emitter.ts +56 -0
- package/src/core/eventNames.ts +53 -0
- package/src/core/retryWait.ts +18 -0
- package/src/core/statusMachine.ts +56 -0
- package/src/core/stream.ts +141 -0
- package/src/core/ticket.ts +146 -0
- package/src/core/types.ts +182 -0
- package/src/core/wire.ts +23 -0
- package/src/createSseClient.test.ts +82 -0
- package/src/createSseClient.ts +34 -0
- package/src/index.ts +21 -0
- package/src/react/SseProvider.test.tsx +129 -0
- package/src/react/createSseContext.test.ts +47 -0
- package/src/react/createSseContext.tsx +140 -0
- package/src/react/index.ts +2 -0
- package/src/react/useSseEvent.test.tsx +71 -0
- package/src/react/useSseStatus.test.tsx +40 -0
- package/src/testing/doubles.ts +128 -0
- package/src/testing/harness.ts +82 -0
- package/src/testing/index.ts +3 -0
- package/src/worker/SseClientLike.contract.test.ts +270 -0
- package/src/worker/WorkerSseClient.scenarios.test.ts +179 -0
- package/src/worker/WorkerSseClient.test.ts +377 -0
- package/src/worker/WorkerSseClient.ts +190 -0
- package/src/worker/hub.connection.test.ts +1023 -0
- package/src/worker/hub.test.ts +213 -0
- package/src/worker/hub.ts +120 -0
- package/src/worker/portRegistry.test.ts +59 -0
- package/src/worker/portRegistry.ts +86 -0
- package/src/worker/protocol.ts +80 -0
- package/src/worker/sse.worker.ts +19 -0
- package/src/worker/ticketDelegate.test.ts +99 -0
- package/src/worker/ticketDelegate.ts +80 -0
- package/src/worker/workerConnection.ts +134 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { SseClient } from './core/SseClient';
|
|
5
|
+
import type { SseClientOptions } from './core/types';
|
|
6
|
+
import { createSseClient } from './createSseClient';
|
|
7
|
+
import { WorkerSseClient } from './worker/WorkerSseClient';
|
|
8
|
+
|
|
9
|
+
const createOptions = (
|
|
10
|
+
overrides?: Partial<SseClientOptions>
|
|
11
|
+
): SseClientOptions & { warnings: string[] } => {
|
|
12
|
+
const warnings: string[] = [];
|
|
13
|
+
return {
|
|
14
|
+
baseUrl: 'https://sse.example.com',
|
|
15
|
+
getAuthToken: () => 'token-1',
|
|
16
|
+
logger: { warn: (message) => warnings.push(message), error: () => {} },
|
|
17
|
+
warnings,
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.unstubAllGlobals();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('createSseClient โ ๊ตฌํ ์ ํ', () => {
|
|
27
|
+
it('sharedWorker ํฉํ ๋ฆฌ๋ฅผ ์ ๊ณตํ๊ณ ๋ธ๋ผ์ฐ์ ๊ฐ SharedWorker๋ฅผ ์ง์ํ๋ฉด ํญ ๊ฐ ๊ณต์ ํด๋ผ์ด์ธํธ๋ฅผ ๋ง๋ญ๋๋ค', () => {
|
|
28
|
+
vi.stubGlobal('SharedWorker', class {});
|
|
29
|
+
const options = createOptions({
|
|
30
|
+
sharedWorker: () => ({}) as SharedWorker,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const client = createSseClient(options);
|
|
34
|
+
|
|
35
|
+
expect(client).toBeInstanceOf(WorkerSseClient);
|
|
36
|
+
expect(options.warnings).toHaveLength(0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('sharedWorker ํฉํ ๋ฆฌ๊ฐ ์์ผ๋ฉด ํญ๋ณ ์ง์ ์ฐ๊ฒฐ ํด๋ผ์ด์ธํธ๋ฅผ ๋ง๋ญ๋๋ค', () => {
|
|
40
|
+
const client = createSseClient(createOptions());
|
|
41
|
+
|
|
42
|
+
expect(client).toBeInstanceOf(SseClient);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('sharedWorker๋ฅผ ์ ๊ณตํด๋ ๋ธ๋ผ์ฐ์ ๊ฐ SharedWorker๋ฅผ ์ง์ํ์ง ์์ผ๋ฉด ์ง์ ์ฐ๊ฒฐ๋ก ๋์ฒดํ๊ณ logger.warn์ผ๋ก ์๋ฆฝ๋๋ค', () => {
|
|
46
|
+
vi.stubGlobal('document', {});
|
|
47
|
+
const options = createOptions({
|
|
48
|
+
sharedWorker: () => ({}) as SharedWorker,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const client = createSseClient(options);
|
|
52
|
+
|
|
53
|
+
expect(client).toBeInstanceOf(SseClient);
|
|
54
|
+
expect(options.warnings).toHaveLength(1);
|
|
55
|
+
expect(options.warnings[0]).toContain('SharedWorker');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('SSRยท์์ปค ์ค์ฝํ(document ์์)์์๋ ์ง์ ์ฐ๊ฒฐ๋ก ๋์ฒดํ๋ ๊ฒฝ๊ณ ํ์ง ์์ต๋๋ค', () => {
|
|
59
|
+
const options = createOptions({
|
|
60
|
+
sharedWorker: () => ({}) as SharedWorker,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const client = createSseClient(options);
|
|
64
|
+
|
|
65
|
+
expect(client).toBeInstanceOf(SseClient);
|
|
66
|
+
expect(options.warnings).toHaveLength(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('์์ปค ๊ฒฝ๋ก์ eventSource๋ฅผ ํจ๊ป ์ฃผ๋ฉด ์ ์ฉ๋์ง ์๋๋ค๊ณ logger.warn์ผ๋ก ์๋ฆฝ๋๋ค', () => {
|
|
70
|
+
vi.stubGlobal('SharedWorker', class {});
|
|
71
|
+
const options = createOptions({
|
|
72
|
+
sharedWorker: () => ({}) as SharedWorker,
|
|
73
|
+
eventSource: class {} as unknown as typeof EventSource,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const client = createSseClient(options);
|
|
77
|
+
|
|
78
|
+
expect(client).toBeInstanceOf(WorkerSseClient);
|
|
79
|
+
expect(options.warnings).toHaveLength(1);
|
|
80
|
+
expect(options.warnings[0]).toContain('eventSource');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SseClient } from './core/SseClient';
|
|
2
|
+
import type {
|
|
3
|
+
SseClientLike,
|
|
4
|
+
SseClientOptions,
|
|
5
|
+
SseEventMap,
|
|
6
|
+
} from './core/types';
|
|
7
|
+
import { WorkerSseClient } from './worker/WorkerSseClient';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @description ํ๊ฒฝ์ ๋ง๋ SseClientLike ๊ตฌํ์ ์ ํํฉ๋๋ค โ sharedWorker ํฉํ ๋ฆฌ๋ฅผ ์ ๊ณตํ๋ฉด
|
|
11
|
+
* ํญ ๊ฐ ์ฐ๊ฒฐ ๊ณต์ (WorkerSseClient), ๋ฏธ์ ๊ณต์ด๊ฑฐ๋ ๋ธ๋ผ์ฐ์ ๊ฐ SharedWorker๋ฅผ ์ง์ํ์ง ์์ผ๋ฉด
|
|
12
|
+
* ํญ๋ณ ์ง์ ์ฐ๊ฒฐ(SseClient)์
๋๋ค. ์์ฑ ์์ ์๋ ๋ฅ๋ ฅ ๊ฒ์ฌ๋ง ํ๋ฏ๋ก SSRยท์์ปค ์ค์ฝํ์์๋ ์์ ํฉ๋๋ค.
|
|
13
|
+
*/
|
|
14
|
+
export const createSseClient = <M extends object = SseEventMap>(
|
|
15
|
+
options: SseClientOptions
|
|
16
|
+
): SseClientLike<M> => {
|
|
17
|
+
if (options.sharedWorker !== undefined) {
|
|
18
|
+
if (options.eventSource !== undefined) {
|
|
19
|
+
options.logger?.warn(
|
|
20
|
+
'sse: ์์ปค ๊ฒฝ๋ก์์๋ eventSource ์ฃผ์
์ด ์ ์ฉ๋์ง ์์ต๋๋ค โ ์คํธ๋ฆผ์ ์์ปค ์ค์ฝํ์์ ๋ง๋ค์ด์ง๋๋ค'
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
if (typeof SharedWorker !== 'undefined') {
|
|
24
|
+
return new WorkerSseClient<M>(options);
|
|
25
|
+
}
|
|
26
|
+
// document๋ ๋ธ๋ผ์ฐ์ ๋ฉ์ธ ์ค๋ ๋์๋ง ์์ต๋๋ค โ SSRยท์์ปค ์ค์ฝํ์ ๋ฌด์๋ฏธํ ๊ฒฝ๊ณ ๋ฅผ ํจ๊ป ๊ฑฐ๋ฆ
๋๋ค
|
|
27
|
+
if (typeof document !== 'undefined') {
|
|
28
|
+
options.logger?.warn(
|
|
29
|
+
'sse: ์ด ๋ธ๋ผ์ฐ์ ๋ SharedWorker๋ฅผ ์ง์ํ์ง ์์ต๋๋ค โ ํญ๋ณ ์ง์ ์ฐ๊ฒฐ๋ก ๋์ฒดํฉ๋๋ค'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return new SseClient<M>(options);
|
|
34
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// ํด๋ผ์ด์ธํธ ์์ฑ์ createSseClient ๋จ์ผ ์ง์
์
๋๋ค โ ๊ตฌํ ํด๋์ค๋ฅผ ์ง์ ๋
ธ์ถํ๋ฉด ์ฃผ์
์ (sharedWorker ์ ํ)์ ์ฐํํ๊ฒ ๋ฉ๋๋ค
|
|
2
|
+
export { createSseClient } from './createSseClient';
|
|
3
|
+
export type { BackoffOptions } from './core/backoff';
|
|
4
|
+
export { wire } from './core/wire';
|
|
5
|
+
export type { WireHandlers } from './core/wire';
|
|
6
|
+
export type {
|
|
7
|
+
FatalCause,
|
|
8
|
+
FatalPayload,
|
|
9
|
+
GoawayAction,
|
|
10
|
+
GoawayPayload,
|
|
11
|
+
GoawayReason,
|
|
12
|
+
PayloadOf,
|
|
13
|
+
SseClientLike,
|
|
14
|
+
SseClientOptions,
|
|
15
|
+
SseEventHandler,
|
|
16
|
+
SseEventMap,
|
|
17
|
+
SseEventName,
|
|
18
|
+
SseLogger,
|
|
19
|
+
SseStatus,
|
|
20
|
+
Unsubscribe,
|
|
21
|
+
} from './core/types';
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { StrictMode } from 'react';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createSseContext } from './createSseContext';
|
|
6
|
+
|
|
7
|
+
import { SseClient } from '../core/SseClient';
|
|
8
|
+
|
|
9
|
+
const { SseProvider } = createSseContext();
|
|
10
|
+
|
|
11
|
+
const options = {
|
|
12
|
+
baseUrl: 'https://sse.example.com',
|
|
13
|
+
getAuthToken: () => 'token',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function trackLifecycleCalls() {
|
|
17
|
+
const calls: string[] = [];
|
|
18
|
+
const instances: unknown[] = [];
|
|
19
|
+
vi.spyOn(SseClient.prototype, 'connect').mockImplementation(
|
|
20
|
+
function (this: unknown) {
|
|
21
|
+
calls.push('connect');
|
|
22
|
+
instances.push(this);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
vi.spyOn(SseClient.prototype, 'disconnect').mockImplementation(
|
|
26
|
+
function (this: unknown) {
|
|
27
|
+
calls.push('disconnect');
|
|
28
|
+
instances.push(this);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
return { calls, instances };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('SseProvider ์ฐ๊ฒฐ ์๋ช
์ฃผ๊ธฐ', () => {
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
vi.restoreAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('StrictMode ์ด์ค ๋ง์ดํธ์์ ๊ฐ์ ์ธ์คํด์ค๋ก connect โ disconnect โ connect๊ฐ ์ํ๋ฉ๋๋ค', () => {
|
|
40
|
+
const { calls, instances } = trackLifecycleCalls();
|
|
41
|
+
const { unmount } = render(
|
|
42
|
+
<StrictMode>
|
|
43
|
+
<SseProvider {...options}>{null}</SseProvider>
|
|
44
|
+
</StrictMode>
|
|
45
|
+
);
|
|
46
|
+
expect(calls).toEqual(['connect', 'disconnect', 'connect']);
|
|
47
|
+
expect(new Set(instances).size).toBe(1);
|
|
48
|
+
unmount();
|
|
49
|
+
expect(calls).toEqual(['connect', 'disconnect', 'connect', 'disconnect']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('enabled=false๋ก ๋ง์ดํธํ๋ฉด ์ฐ๊ฒฐํ ์ ์๋ ํด๋ผ์ด์ธํธ์ disconnect๊ฐ ๋๊ฐ์ง ์์ต๋๋ค', () => {
|
|
53
|
+
const { calls } = trackLifecycleCalls();
|
|
54
|
+
const { unmount } = render(
|
|
55
|
+
<StrictMode>
|
|
56
|
+
<SseProvider {...options} enabled={false}>
|
|
57
|
+
{null}
|
|
58
|
+
</SseProvider>
|
|
59
|
+
</StrictMode>
|
|
60
|
+
);
|
|
61
|
+
unmount();
|
|
62
|
+
expect(calls).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('enabled๋ฅผ ๋๋ฉด ์ด์ effect์ cleanup์ด ํ ๋ฒ๋ง ํด์ ํฉ๋๋ค', () => {
|
|
66
|
+
const { calls } = trackLifecycleCalls();
|
|
67
|
+
const { rerender } = render(<SseProvider {...options}>{null}</SseProvider>);
|
|
68
|
+
rerender(
|
|
69
|
+
<SseProvider {...options} enabled={false}>
|
|
70
|
+
{null}
|
|
71
|
+
</SseProvider>
|
|
72
|
+
);
|
|
73
|
+
expect(calls).toEqual(['connect', 'disconnect']);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('enabled๋ฅผ ๋ค์ ์ผ๋ฉด ์ฌ์ฐ๊ฒฐํ๊ณ , ๊ทธ ์ฐ๊ฒฐ์ ์ธ๋ง์ดํธ ๋ ํด์ ๋ฉ๋๋ค', () => {
|
|
77
|
+
const { calls } = trackLifecycleCalls();
|
|
78
|
+
const { rerender, unmount } = render(
|
|
79
|
+
<SseProvider {...options} enabled={false}>
|
|
80
|
+
{null}
|
|
81
|
+
</SseProvider>
|
|
82
|
+
);
|
|
83
|
+
rerender(<SseProvider {...options}>{null}</SseProvider>);
|
|
84
|
+
expect(calls).toEqual(['connect']);
|
|
85
|
+
unmount();
|
|
86
|
+
expect(calls).toEqual(['connect', 'disconnect']);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ์ด ๋ฐฐ์ ์ด ์์ผ๋ฉด ํด๋ฉด์ผ๋ก ์ ํ ์ฐ๊ฒฐ์ด ๊นจ์ด๋๋ ์์ง ๋ชปํด, ๋ฐฉ์นํ ํญ์ ์์ ์ด ์๋ก๊ณ ์นจ ์ ๊น์ง ์ฃฝ์ด ์์ต๋๋ค
|
|
91
|
+
describe('SseProvider ์๋ ์ฌ๊ฐ', () => {
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
vi.restoreAllMocks();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('ํญ์ด ๋ค์ ๋ณด์ด๋ฉด connect๋ฅผ ๋ค์ ๋ถ๋ฆ
๋๋ค', () => {
|
|
97
|
+
const { calls } = trackLifecycleCalls();
|
|
98
|
+
render(<SseProvider {...options}>{null}</SseProvider>);
|
|
99
|
+
expect(calls).toEqual(['connect']);
|
|
100
|
+
|
|
101
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
102
|
+
|
|
103
|
+
expect(calls).toEqual(['connect', 'connect']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('์ธ๋ง์ดํธํ ๋ค์๋ ํญ์ด ๋ค์ ๋ณด์ฌ๋ connect๊ฐ ๋๊ฐ์ง ์์ต๋๋ค', () => {
|
|
107
|
+
const { calls } = trackLifecycleCalls();
|
|
108
|
+
const { unmount } = render(<SseProvider {...options}>{null}</SseProvider>);
|
|
109
|
+
unmount();
|
|
110
|
+
const before = [...calls];
|
|
111
|
+
|
|
112
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
113
|
+
|
|
114
|
+
expect(calls).toEqual(before);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('enabled=false๋ฉด ์๋ ์ฌ๊ฐ๋ ๋ฐฐ์ ๋์ง ์์ต๋๋ค', () => {
|
|
118
|
+
const { calls } = trackLifecycleCalls();
|
|
119
|
+
render(
|
|
120
|
+
<SseProvider {...options} enabled={false}>
|
|
121
|
+
{null}
|
|
122
|
+
</SseProvider>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
126
|
+
|
|
127
|
+
expect(calls).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { createSseContext } from './createSseContext';
|
|
5
|
+
|
|
6
|
+
import type { FatalPayload } from '../core/types';
|
|
7
|
+
|
|
8
|
+
// ์ด ํ์ผ์ ๊ฒ์ฆ ๋์์ ํ์
์ถ๋ก ์
๋๋ค โ ํ
ํธ์ถ ์์ด ์ธ์คํด์คํ ํํ์์ผ๋ก๋ง ํ์ธํ๋ฏ๋ก Provider๊ฐ ํ์ ์์ต๋๋ค
|
|
9
|
+
// ๊ฒ์ดํธ๋ tsc(type-check)์
๋๋ค โ vitest run์ ํ์
๋จ์ธ์ ๊ฒ์ฆํ์ง ์์ต๋๋ค
|
|
10
|
+
interface TestEventMap {
|
|
11
|
+
'demo.created': { id: string };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('createSseContext ํ์
์ถ๋ก ', () => {
|
|
15
|
+
const hooks = createSseContext<TestEventMap>();
|
|
16
|
+
|
|
17
|
+
it('๋งต์ ์๋ ์ด๋ฒคํธ๋ช
์ด๋ฉด ํ์ด๋ก๋๊ฐ ๋งต์์ ์ถ๋ก ๋ฉ๋๋ค', () => {
|
|
18
|
+
expectTypeOf(hooks.useSseEvent<'demo.created'>)
|
|
19
|
+
.parameter(1)
|
|
20
|
+
.toEqualTypeOf<(payload: { id: string }) => void>();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('๋งต์ ์๋ ์ด๋ฒคํธ๋ช
์ unknown ํ์ด๋ก๋๋ก ํ์ฉ๋ฉ๋๋ค', () => {
|
|
24
|
+
expectTypeOf(hooks.useSseEvent<'not.in.map'>)
|
|
25
|
+
.parameter(1)
|
|
26
|
+
.toEqualTypeOf<(payload: unknown) => void>();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('SDK ์์ฝ ์ด๋ฒคํธ resync์ ํ์ด๋ก๋๋ void์
๋๋ค', () => {
|
|
30
|
+
expectTypeOf(hooks.useSseEvent<'resync'>)
|
|
31
|
+
.parameter(1)
|
|
32
|
+
.toEqualTypeOf<(payload: void) => void>();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('SDK ์์ฝ ์ด๋ฒคํธ fatal์ ํ์ด๋ก๋๋ ๋ฉ์ถ ์ฌ์ ๋ฅผ ๋ด์ต๋๋ค', () => {
|
|
36
|
+
expectTypeOf(hooks.useSseEvent<'fatal'>)
|
|
37
|
+
.parameter(1)
|
|
38
|
+
.toEqualTypeOf<(payload: FatalPayload) => void>();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('๋งต๊ณผ ๋ค๋ฅธ ํ์ด๋ก๋ ํ์
์ ํธ๋ค๋ฌ๋ ์ปดํ์ผ์์ ๊ฑฐ๋ถ๋ฉ๋๋ค', () => {
|
|
42
|
+
type DemoHandler = Parameters<typeof hooks.useSseEvent<'demo.created'>>[1];
|
|
43
|
+
// @ts-expect-error 'demo.created'์ ํ์ด๋ก๋๋ { id: string }์
๋๋ค
|
|
44
|
+
const wrong: DemoHandler = (e: { wrong: number }) => e.wrong;
|
|
45
|
+
expectTypeOf(wrong).toBeFunction();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useInsertionEffect,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
useSyncExternalStore,
|
|
11
|
+
} from 'react';
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
PayloadOf,
|
|
15
|
+
SseClientLike,
|
|
16
|
+
SseClientOptions,
|
|
17
|
+
SseEventHandler,
|
|
18
|
+
SseEventMap,
|
|
19
|
+
SseEventName,
|
|
20
|
+
SseStatus,
|
|
21
|
+
} from '../core/types';
|
|
22
|
+
import { createSseClient } from '../createSseClient';
|
|
23
|
+
|
|
24
|
+
// useEffectEvent(React 19.2+)์ ๊ฐ์ ๋ชฉ์ ์ latest-ref ํจํด โ insertion effect๋ ์ปค๋ฐ๋ ๋ ๋์์๋ง ์คํ๋๊ณ paint ์ ์ ๋๊ธฐ ๋ฐ์๋ฉ๋๋ค
|
|
25
|
+
const useLatestRef = <T,>(value: T) => {
|
|
26
|
+
const ref = useRef(value);
|
|
27
|
+
useInsertionEffect(() => {
|
|
28
|
+
ref.current = value;
|
|
29
|
+
});
|
|
30
|
+
return ref;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @description ์ฐ๊ฒฐ ์ต์
์ ๋ง์ดํธ ์์ ๊ฐ์ผ๋ก ๊ณ ์ ๋ฉ๋๋ค โ ์ดํ prop ๋ณ๊ฒฝ์ ๋ฌด์๋ฉ๋๋ค.
|
|
35
|
+
* ๋จ `getAuthToken`์ ๋ง์ง๋ง ์ปค๋ฐ๋ ๋ ๋์ ํจ์๋ฅผ ํธ์ถํ๋ฏ๋ก ์ธ๋ผ์ธ ํด๋ก์ ๋ฅผ ๋๊ฒจ๋ ์์ ํฉ๋๋ค.
|
|
36
|
+
*/
|
|
37
|
+
export interface SseProviderProps extends SseClientOptions {
|
|
38
|
+
/** @description false๋ฉด ์ฐ๊ฒฐํ์ง ์๊ณ , ์ฐ๊ฒฐ ์ค์ด๋ฉด ํด์ ํฉ๋๋ค (๋ก๊ทธ์์ ๋ฑ) */
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
children: ReactNode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @description ์ฑ์ ์ด๋ฒคํธ ๋งต์ผ๋ก Provider์ ํ
์ ํ ๋ฒ์ ์์ฑํฉ๋๋ค โ ํธ์ถ๋ถ๋ ์ด๋ฒคํธ๋ช
๋ง์ผ๋ก ํ์ด๋ก๋๊ฐ ์ถ๋ก ๋ฉ๋๋ค.
|
|
45
|
+
* ์ปจํ
์คํธ๋ฅผ ํฉํ ๋ฆฌ ์์์ ๋ง๋ค์ด Provider์ ํ
์ด ํ์
์ผ๋ก ๊ฒฐํฉ๋๋ฉฐ,
|
|
46
|
+
* ํธ์ถ๋ง๋ค ์ ๋ค๋ฆญ์ ๋ช
์ํ๋ ๋ฐฉ์์ TS์ ๋ถ๋ถ ํ์
์ธ์ ์ถ๋ก ๋ถ์ฌ๋ก ํ์ด๋ก๋๊ฐ unknown์ด ๋์ด ํฉํ ๋ฆฌ๋ก ๊ณ ์ ํฉ๋๋ค.
|
|
47
|
+
* ์ปดํฌ๋ํธ ๋ฐ ๋ชจ๋ ์ค์ฝํ์์ ํ ๋ฒ๋ง ํธ์ถํ์ธ์ โ ๋ ๋ ์ค ํธ์ถํ๋ฉด ์ปจํ
์คํธ๊ฐ ๋งค๋ฒ ์ฌ์์ฑ๋ฉ๋๋ค.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // ์ฑ๋น 1ํ, ๋ชจ๋ ์ค์ฝํ
|
|
51
|
+
* export const { SseProvider, useSseEvent, useSseStatus } =
|
|
52
|
+
* createSseContext<AppEventMap>();
|
|
53
|
+
*
|
|
54
|
+
* // ํธ์ถ๋ถ โ ์ด๋ฒคํธ๋ช
๋ง์ผ๋ก ํ์ด๋ก๋๊ฐ ์ถ๋ก ๋ฉ๋๋ค
|
|
55
|
+
* useSseEvent('demo.created', (event) => showToast(event.id));
|
|
56
|
+
*/
|
|
57
|
+
export function createSseContext<M extends object = SseEventMap>() {
|
|
58
|
+
const SseClientContext = createContext<SseClientLike<M> | null>(null);
|
|
59
|
+
|
|
60
|
+
const SseProvider = ({
|
|
61
|
+
enabled = true,
|
|
62
|
+
children,
|
|
63
|
+
...options
|
|
64
|
+
}: Readonly<SseProviderProps>) => {
|
|
65
|
+
const getAuthTokenRef = useLatestRef(options.getAuthToken);
|
|
66
|
+
|
|
67
|
+
const [client] = useState(() =>
|
|
68
|
+
createSseClient<M>({
|
|
69
|
+
...options,
|
|
70
|
+
getAuthToken: () => getAuthTokenRef.current(),
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!enabled) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
client.connect();
|
|
79
|
+
// ํด๋ฉด์ผ๋ก ์ ํ ์ฐ๊ฒฐ์ ๊นจ์ด๋ ํญ์ connect๋ง์ด ๋ค์ ์ธ์๋๋ค โ ์๋น์ฒ๊ฐ ๋ชฐ๋ผ๋ ๋ณต๊ตฌ๋๊ฒ ์ฌ๊ธฐ์ ๋ถ์
๋๋ค
|
|
80
|
+
const detachAutoResume = client.attachAutoResume();
|
|
81
|
+
return () => {
|
|
82
|
+
detachAutoResume();
|
|
83
|
+
client.disconnect();
|
|
84
|
+
};
|
|
85
|
+
}, [client, enabled]);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<SseClientContext.Provider value={client}>
|
|
89
|
+
{children}
|
|
90
|
+
</SseClientContext.Provider>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const useSseClient = (): SseClientLike<M> => {
|
|
95
|
+
const client = useContext(SseClientContext);
|
|
96
|
+
if (!client) {
|
|
97
|
+
throw new Error('SSE ํ
์ SseProvider ์์์๋ง ํธ์ถํ ์ ์์ต๋๋ค');
|
|
98
|
+
}
|
|
99
|
+
return client;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @description ์ด๋ฒคํธ ๊ตฌ๋
ํ
โ ๋ง์ดํธ ๋์ ๊ตฌ๋
ํ๊ณ ์ธ๋ง์ดํธ์ ํด์ ํ๋ฉฐ, ํธ๋ค๋ฌ๋ ํญ์ ์ต์ ๋ ๋์ ํด๋ก์ ๋ฅผ ๋ด
๋๋ค.
|
|
104
|
+
* @param type ์ด๋ฒคํธ ์ด๋ฆ. ๋๋ฉ์ธ ์ด๋ฒคํธ ์ธ ์์ฝ 4์ข
โ
|
|
105
|
+
* - `fatal`(SDK์ ํฌ๊ธฐ ์ ์ธ โ ์ฌ์๋ ์์ด ๋ฉ์ถค, SDK ํฉ์ฑ) ยท
|
|
106
|
+
* - `stalled`(์ฌ์๋๊ฐ ๊ณ์๋๋ ์งํํ ํต์ง, SDK ํฉ์ฑ) ยท
|
|
107
|
+
* - `goaway`(์๋ฒ์ ์๊ณ ๋ ๋์ โ `action: 'close'`๋ง ์๋ด ๋์, ์๋ฒ ๋ฐ์ ) ยท
|
|
108
|
+
* - `resync`(์ฌ์๋ฆฝ ํ ๋์น ๊ตฌ๊ฐ์ REST๋ก ์ฌ์กฐํํ๋ผ๋ ์ ํธ, SDK ํฉ์ฑ). ์์ธ๋ `WithLifecycle` ๋ฌธ์ ์ฐธ์กฐ.
|
|
109
|
+
* @param handler ์ด๋ฒคํธ ํ์ด๋ก๋๋ฅผ ๋ฐ๋ ์ฝ๋ฐฑ โ ๋ฐํ ์๊ฐ์ ๊ตฌ๋
์์๊ฒ๋ง ๋ฐฐ๋ฌ๋๋ฏ๋ก ์๋ช
์ฃผ๊ธฐ ํธ๋ค๋ฌ๋ ์ฑ ๋ฃจํธ์ ๋์ธ์
|
|
110
|
+
*/
|
|
111
|
+
const useSseEvent = <K extends SseEventName<M>>(
|
|
112
|
+
type: K,
|
|
113
|
+
handler: SseEventHandler<PayloadOf<M, K>>
|
|
114
|
+
): void => {
|
|
115
|
+
const client = useSseClient();
|
|
116
|
+
const handlerRef = useLatestRef(handler);
|
|
117
|
+
|
|
118
|
+
useEffect(
|
|
119
|
+
() => client.on(type, (payload) => handlerRef.current(payload)),
|
|
120
|
+
// handlerRef๋ useRef ์ ์ฒด์ฑ์ด๋ผ ์ฌ๊ตฌ๋
์ ์ ๋ฐํ์ง ์์ง๋ง, exhaustive-deps๊ฐ ์ปค์คํ
ํ
๋ฐํ์ ์์ ์ผ๋ก ๋ณผ ์ ์์ด ๋ช
์ํฉ๋๋ค
|
|
121
|
+
[client, type, handlerRef]
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const useSseStatus = (): SseStatus => {
|
|
126
|
+
const client = useSseClient();
|
|
127
|
+
const subscribe = useCallback(
|
|
128
|
+
(onStoreChange: () => void) => client.onStatus(onStoreChange),
|
|
129
|
+
[client]
|
|
130
|
+
);
|
|
131
|
+
return useSyncExternalStore(
|
|
132
|
+
subscribe,
|
|
133
|
+
() => client.status,
|
|
134
|
+
// ์๋ฒ/hydration ์ฒซ ๋ ๋๋ connect() ์ด์ ์ด๋ผ ํญ์ ํด๋ผ์ด์ธํธ ์ด๊ธฐ ์ํ์
๋๋ค
|
|
135
|
+
() => client.status
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return { SseProvider, useSseClient, useSseEvent, useSseStatus };
|
|
140
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { createSseContext } from './createSseContext';
|
|
5
|
+
|
|
6
|
+
import { SseClient } from '../core/SseClient';
|
|
7
|
+
|
|
8
|
+
interface TestEventMap {
|
|
9
|
+
'demo.created': { id: string };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { SseProvider, useSseEvent } = createSseContext<TestEventMap>();
|
|
13
|
+
|
|
14
|
+
const options = {
|
|
15
|
+
baseUrl: 'https://sse.example.com',
|
|
16
|
+
getAuthToken: () => 'token',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function EventProbe({ onCreated }: { onCreated: (e: { id: string }) => void }) {
|
|
20
|
+
useSseEvent('demo.created', onCreated);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('useSseEvent', () => {
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
vi.restoreAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('๋ฆฌ๋ ๋ ์ ์ฌ๊ตฌ๋
์์ด, ์ด๋ฒคํธ๊ฐ ์ค๋ฉด ์ต์ ํธ๋ค๋ฌ๊ฐ ํธ์ถ๋ฉ๋๋ค', () => {
|
|
30
|
+
let captured: ((payload: unknown) => void) | undefined;
|
|
31
|
+
const on = vi
|
|
32
|
+
.spyOn(SseClient.prototype, 'on')
|
|
33
|
+
.mockImplementation((_type, handler) => {
|
|
34
|
+
captured = handler as (payload: unknown) => void;
|
|
35
|
+
return () => {};
|
|
36
|
+
});
|
|
37
|
+
const staleHandler = vi.fn();
|
|
38
|
+
const latestHandler = vi.fn();
|
|
39
|
+
const { rerender } = render(
|
|
40
|
+
<SseProvider {...options}>
|
|
41
|
+
<EventProbe onCreated={staleHandler} />
|
|
42
|
+
</SseProvider>
|
|
43
|
+
);
|
|
44
|
+
rerender(
|
|
45
|
+
<SseProvider {...options}>
|
|
46
|
+
<EventProbe onCreated={latestHandler} />
|
|
47
|
+
</SseProvider>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
captured?.({ id: 'a' });
|
|
51
|
+
|
|
52
|
+
expect(on).toHaveBeenCalledTimes(1);
|
|
53
|
+
expect(staleHandler).not.toHaveBeenCalled();
|
|
54
|
+
expect(latestHandler).toHaveBeenCalledWith({ id: 'a' });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('์ธ๋ง์ดํธํ๋ฉด ๊ตฌ๋
์ด ํด์ ๋ฉ๋๋ค', () => {
|
|
58
|
+
const unsubscribe = vi.fn();
|
|
59
|
+
vi.spyOn(SseClient.prototype, 'on').mockImplementation(() => unsubscribe);
|
|
60
|
+
const { unmount } = render(
|
|
61
|
+
<SseProvider {...options}>
|
|
62
|
+
<EventProbe onCreated={() => {}} />
|
|
63
|
+
</SseProvider>
|
|
64
|
+
);
|
|
65
|
+
expect(unsubscribe).not.toHaveBeenCalled();
|
|
66
|
+
|
|
67
|
+
unmount();
|
|
68
|
+
|
|
69
|
+
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { createSseContext } from './createSseContext';
|
|
5
|
+
|
|
6
|
+
import { SseClient } from '../core/SseClient';
|
|
7
|
+
|
|
8
|
+
const { SseProvider, useSseStatus } = createSseContext();
|
|
9
|
+
|
|
10
|
+
const options = {
|
|
11
|
+
baseUrl: 'https://sse.example.com',
|
|
12
|
+
getAuthToken: () => 'token',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function StatusProbe() {
|
|
16
|
+
useSseStatus();
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('useSseStatus', () => {
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.restoreAllMocks();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('๋ฆฌ๋ ๋ ์ ์ฌ๊ตฌ๋
ํ์ง ์์ต๋๋ค', () => {
|
|
26
|
+
const onStatus = vi.spyOn(SseClient.prototype, 'onStatus');
|
|
27
|
+
const { rerender, unmount } = render(
|
|
28
|
+
<SseProvider {...options}>
|
|
29
|
+
<StatusProbe />
|
|
30
|
+
</SseProvider>
|
|
31
|
+
);
|
|
32
|
+
rerender(
|
|
33
|
+
<SseProvider {...options}>
|
|
34
|
+
<StatusProbe />
|
|
35
|
+
</SseProvider>
|
|
36
|
+
);
|
|
37
|
+
expect(onStatus).toHaveBeenCalledTimes(1);
|
|
38
|
+
unmount();
|
|
39
|
+
});
|
|
40
|
+
});
|