@connexis/react 1.0.0 → 1.0.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/package.json +18 -2
- package/src/__tests__/react-bindings.test.ts +152 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connexis/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "React hooks and provider for @connexis",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -16,13 +16,29 @@
|
|
|
16
16
|
"react": "^18.0.0 || ^19.0.0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@connexis/core": "1.0.
|
|
19
|
+
"@connexis/core": "1.0.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/react": "^18.3.3",
|
|
23
23
|
"react": "^18.3.1",
|
|
24
24
|
"typescript": "^5.5.2"
|
|
25
25
|
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/HarpalSingh7395/connexis.git"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"realtime",
|
|
32
|
+
"connection-manager",
|
|
33
|
+
"react",
|
|
34
|
+
"hooks",
|
|
35
|
+
"websocket",
|
|
36
|
+
"sse",
|
|
37
|
+
"typescript"
|
|
38
|
+
],
|
|
39
|
+
"author": "HarpalSingh7395 <harrysinghharpal58@gmail.com>",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"homepage": "https://HarpalSingh7395.github.io/connexis/",
|
|
26
42
|
"scripts": {
|
|
27
43
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean"
|
|
28
44
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { useContext, useState, useEffect, useRef } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
useRealtime,
|
|
5
|
+
useConnection,
|
|
6
|
+
useSubscription,
|
|
7
|
+
usePublish
|
|
8
|
+
} from '../index.js';
|
|
9
|
+
|
|
10
|
+
// Setup Mock Client
|
|
11
|
+
class MockClient {
|
|
12
|
+
public state = 'connected';
|
|
13
|
+
public metrics = {
|
|
14
|
+
reconnectCount: 0,
|
|
15
|
+
latency: 5,
|
|
16
|
+
uptime: 10,
|
|
17
|
+
transportType: 'websocket',
|
|
18
|
+
connectionCount: 1,
|
|
19
|
+
activeSubscriptions: 1,
|
|
20
|
+
leaderChanges: 0,
|
|
21
|
+
throughput: { inbound: 0, outbound: 0 }
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
private listeners = new Map<string, Set<(...args: any[]) => void>>();
|
|
25
|
+
|
|
26
|
+
on(event: string, callback: (...args: any[]) => void) {
|
|
27
|
+
if (!this.listeners.has(event)) {
|
|
28
|
+
this.listeners.set(event, new Set());
|
|
29
|
+
}
|
|
30
|
+
this.listeners.get(event)!.add(callback);
|
|
31
|
+
return () => {
|
|
32
|
+
this.listeners.get(event)!.delete(callback);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
emit(event: string, data: any) {
|
|
37
|
+
this.listeners.get(event)?.forEach((cb) => cb(data));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
subscribe = vi.fn().mockImplementation(() => {
|
|
41
|
+
return Promise.resolve(vi.fn().mockResolvedValue(undefined));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
publish = vi.fn().mockResolvedValue(undefined);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
vi.mock('react', async (importOriginal) => {
|
|
48
|
+
const actual = await importOriginal<any>();
|
|
49
|
+
return {
|
|
50
|
+
...actual,
|
|
51
|
+
useContext: vi.fn(),
|
|
52
|
+
useState: vi.fn(),
|
|
53
|
+
useEffect: vi.fn(),
|
|
54
|
+
useRef: vi.fn(),
|
|
55
|
+
useCallback: vi.fn((fn) => fn)
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('@connexis/react Hooks', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
vi.clearAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('useRealtime should throw if context is missing', () => {
|
|
65
|
+
vi.mocked(useContext).mockReturnValue(null);
|
|
66
|
+
expect(() => useRealtime()).toThrow('useRealtime must be used within a RealtimeProvider');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('useRealtime should return the client if provider is present', () => {
|
|
70
|
+
const client = new MockClient();
|
|
71
|
+
vi.mocked(useContext).mockReturnValue(client);
|
|
72
|
+
expect(useRealtime()).toBe(client);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('usePublish should return a callback that invokes client.publish()', async () => {
|
|
76
|
+
const client = new MockClient();
|
|
77
|
+
vi.mocked(useContext).mockReturnValue(client);
|
|
78
|
+
|
|
79
|
+
const publish = usePublish();
|
|
80
|
+
await publish('my_topic', { data: 'hello' });
|
|
81
|
+
|
|
82
|
+
expect(client.publish).toHaveBeenCalledWith('my_topic', { data: 'hello' });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('useConnection should sync state and metrics and listen to changes', () => {
|
|
86
|
+
const client = new MockClient();
|
|
87
|
+
vi.mocked(useContext).mockReturnValue(client);
|
|
88
|
+
|
|
89
|
+
const mockSetState = vi.fn();
|
|
90
|
+
const mockSetMetrics = vi.fn();
|
|
91
|
+
|
|
92
|
+
vi.mocked(useState)
|
|
93
|
+
.mockImplementationOnce((init) => [init, mockSetState])
|
|
94
|
+
.mockImplementationOnce((init) => [init, mockSetMetrics]);
|
|
95
|
+
|
|
96
|
+
// Stub useEffect to invoke the effect immediately
|
|
97
|
+
vi.mocked(useEffect).mockImplementationOnce((effect) => {
|
|
98
|
+
effect();
|
|
99
|
+
return undefined;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const { state, metrics } = useConnection();
|
|
103
|
+
|
|
104
|
+
expect(state).toBe('connected');
|
|
105
|
+
expect(metrics.latency).toBe(5);
|
|
106
|
+
|
|
107
|
+
// Emit event and verify state setter is called
|
|
108
|
+
client.emit('stateChange', { state: 'connecting' });
|
|
109
|
+
expect(mockSetState).toHaveBeenCalledWith('connecting');
|
|
110
|
+
|
|
111
|
+
// Emit metrics and verify metrics setter is called
|
|
112
|
+
const newMetrics = { ...client.metrics, latency: 15 };
|
|
113
|
+
client.emit('metricsChange', newMetrics);
|
|
114
|
+
expect(mockSetMetrics).toHaveBeenCalledWith(newMetrics);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('useSubscription should register subscribe on mount and unsubscribe on unmount', async () => {
|
|
118
|
+
const client = new MockClient();
|
|
119
|
+
vi.mocked(useContext).mockReturnValue(client);
|
|
120
|
+
|
|
121
|
+
const mockUnsub = vi.fn().mockResolvedValue(undefined);
|
|
122
|
+
client.subscribe.mockResolvedValue(mockUnsub);
|
|
123
|
+
|
|
124
|
+
let effectCleanup: (() => void) | undefined;
|
|
125
|
+
|
|
126
|
+
vi.mocked(useEffect).mockImplementationOnce((effect) => {
|
|
127
|
+
effectCleanup = effect() as any;
|
|
128
|
+
return undefined;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Mock useRef behavior
|
|
132
|
+
const refObject = { current: null };
|
|
133
|
+
vi.mocked(useRef).mockReturnValue(refObject);
|
|
134
|
+
|
|
135
|
+
const handler = vi.fn();
|
|
136
|
+
useSubscription('ticks', handler);
|
|
137
|
+
|
|
138
|
+
expect(client.subscribe).toHaveBeenCalledWith('ticks', {}, expect.any(Function));
|
|
139
|
+
|
|
140
|
+
// Verify trigger handler callback
|
|
141
|
+
const subscribeCallback = client.subscribe.mock.calls[0][2] as (...args: any[]) => void;
|
|
142
|
+
subscribeCallback('price_data');
|
|
143
|
+
expect(handler).toHaveBeenCalledWith('price_data');
|
|
144
|
+
|
|
145
|
+
// Trigger cleanup and check unsubscribe
|
|
146
|
+
if (effectCleanup) {
|
|
147
|
+
effectCleanup();
|
|
148
|
+
}
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
150
|
+
expect(mockUnsub).toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
});
|