@dhis2/app-service-offline 3.8.0 → 3.9.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.
- package/build/cjs/__tests__/integration.test.js +37 -24
- package/build/cjs/index.js +18 -10
- package/build/cjs/lib/__tests__/clear-sensitive-caches.test.js +5 -1
- package/build/cjs/lib/__tests__/{online-status.test.js → network-status.test.js} +41 -20
- package/build/cjs/lib/__tests__/offline-provider.test.js +5 -1
- package/build/cjs/lib/__tests__/use-cacheable-section.test.js +77 -35
- package/build/cjs/lib/__tests__/{use-online-staus-message.test.js → use-online-status-message.test.js} +8 -5
- package/build/cjs/lib/cacheable-section-state.js +17 -13
- package/build/cjs/lib/cacheable-section.js +14 -12
- package/build/cjs/lib/clear-sensitive-caches.js +9 -5
- package/build/cjs/lib/dhis2-connection-status/dev-debug-log.js +26 -0
- package/build/cjs/lib/dhis2-connection-status/dhis2-connection-status.js +224 -0
- package/build/cjs/lib/dhis2-connection-status/dhis2-connection-status.test.js +841 -0
- package/build/cjs/lib/dhis2-connection-status/index.js +19 -0
- package/build/cjs/lib/dhis2-connection-status/is-ping-available.js +43 -0
- package/build/cjs/lib/dhis2-connection-status/is-ping-available.test.js +76 -0
- package/build/cjs/lib/dhis2-connection-status/smart-interval.js +211 -0
- package/build/cjs/lib/dhis2-connection-status/use-ping-query.js +21 -0
- package/build/cjs/lib/global-state-service.js +16 -11
- package/build/cjs/lib/{online-status.js → network-status.js} +8 -5
- package/build/cjs/lib/offline-interface.js +7 -4
- package/build/cjs/lib/offline-provider.js +9 -5
- package/build/cjs/lib/online-status-message.js +5 -4
- package/build/cjs/utils/render-counter.js +6 -4
- package/build/cjs/utils/test-mocks.js +17 -10
- package/build/es/__tests__/integration.test.js +37 -24
- package/build/es/index.js +5 -3
- package/build/es/lib/__tests__/clear-sensitive-caches.test.js +5 -1
- package/build/es/lib/__tests__/{online-status.test.js → network-status.test.js} +35 -14
- package/build/es/lib/__tests__/offline-provider.test.js +5 -1
- package/build/es/lib/__tests__/use-cacheable-section.test.js +77 -34
- package/build/es/lib/__tests__/{use-online-staus-message.test.js → use-online-status-message.test.js} +8 -5
- package/build/es/lib/cacheable-section-state.js +14 -10
- package/build/es/lib/cacheable-section.js +13 -11
- package/build/es/lib/clear-sensitive-caches.js +8 -4
- package/build/es/lib/dhis2-connection-status/dev-debug-log.js +20 -0
- package/build/es/lib/dhis2-connection-status/dhis2-connection-status.js +194 -0
- package/build/es/lib/dhis2-connection-status/dhis2-connection-status.test.js +831 -0
- package/build/es/lib/dhis2-connection-status/index.js +1 -0
- package/build/es/lib/dhis2-connection-status/is-ping-available.js +36 -0
- package/build/es/lib/dhis2-connection-status/is-ping-available.test.js +73 -0
- package/build/es/lib/dhis2-connection-status/smart-interval.js +199 -0
- package/build/es/lib/dhis2-connection-status/use-ping-query.js +12 -0
- package/build/es/lib/global-state-service.js +15 -10
- package/build/es/lib/{online-status.js → network-status.js} +7 -4
- package/build/es/lib/offline-interface.js +7 -4
- package/build/es/lib/offline-provider.js +8 -5
- package/build/es/lib/online-status-message.js +4 -3
- package/build/es/utils/render-counter.js +6 -4
- package/build/es/utils/test-mocks.js +16 -9
- package/build/types/index.d.ts +2 -1
- package/build/types/lib/dhis2-connection-status/dev-debug-log.d.ts +9 -0
- package/build/types/lib/dhis2-connection-status/dhis2-connection-status.d.ts +28 -0
- package/build/types/lib/dhis2-connection-status/index.d.ts +1 -0
- package/build/types/lib/dhis2-connection-status/is-ping-available.d.ts +14 -0
- package/build/types/lib/dhis2-connection-status/smart-interval.d.ts +18 -0
- package/build/types/lib/dhis2-connection-status/use-ping-query.d.ts +1 -0
- package/build/types/lib/{online-status.d.ts → network-status.d.ts} +3 -3
- package/build/types/types.d.ts +6 -0
- package/build/types/utils/test-mocks.d.ts +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
import { ConfigProvider } from '@dhis2/app-service-config';
|
|
2
|
+
import { renderHook, act } from '@testing-library/react-hooks';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { mockOfflineInterface } from '../../utils/test-mocks';
|
|
5
|
+
import { OfflineProvider } from '../offline-provider';
|
|
6
|
+
import { getLastConnectedKey, useDhis2ConnectionStatus } from './dhis2-connection-status';
|
|
7
|
+
import { DEFAULT_INCREMENT_FACTOR, DEFAULT_MAX_DELAY_MS, DEFAULT_INITIAL_DELAY_MS } from './smart-interval'; // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
8
|
+
|
|
9
|
+
// important that this name starts with 'mock' to be hoisted correctly
|
|
10
|
+
const mockPing = jest.fn().mockImplementation(() => Promise.resolve());
|
|
11
|
+
jest.mock('./use-ping-query.ts', () => ({
|
|
12
|
+
usePingQuery: () => mockPing
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const failedPing = () => Promise.reject({
|
|
16
|
+
message: 'this is a network error',
|
|
17
|
+
type: 'network'
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const FIRST_INTERVAL_MS = DEFAULT_INITIAL_DELAY_MS;
|
|
21
|
+
const SECOND_INTERVAL_MS = FIRST_INTERVAL_MS * DEFAULT_INCREMENT_FACTOR;
|
|
22
|
+
const THIRD_INTERVAL_MS = SECOND_INTERVAL_MS * DEFAULT_INCREMENT_FACTOR;
|
|
23
|
+
const FOURTH_INTERVAL_MS = THIRD_INTERVAL_MS * DEFAULT_INCREMENT_FACTOR; // Explanation: The length of the Nth interval is:
|
|
24
|
+
// initialDelay * incrementFactor ^ (N - 1)
|
|
25
|
+
// Using some algebra and the law of logs, the Nth interval
|
|
26
|
+
// which is longer than the max delay is:
|
|
27
|
+
// N >= (ln (maxDelay / initialDelay) / ln (incrementFactor)) + 1
|
|
28
|
+
// => then use Math.ceil to handle the 'greater than' effect
|
|
29
|
+
|
|
30
|
+
const INTERVALS_TO_REACH_MAX_DELAY = Math.ceil(Math.log(DEFAULT_MAX_DELAY_MS / DEFAULT_INITIAL_DELAY_MS) / Math.log(DEFAULT_INCREMENT_FACTOR) + 1);
|
|
31
|
+
|
|
32
|
+
const wrapper = (_ref) => {
|
|
33
|
+
let {
|
|
34
|
+
children
|
|
35
|
+
} = _ref;
|
|
36
|
+
return /*#__PURE__*/React.createElement(ConfigProvider, {
|
|
37
|
+
config: {
|
|
38
|
+
baseUrl: '..',
|
|
39
|
+
apiVersion: 42,
|
|
40
|
+
// ensure this is a server version where pings are enabled
|
|
41
|
+
serverVersion: {
|
|
42
|
+
major: 2,
|
|
43
|
+
minor: 40,
|
|
44
|
+
patch: 0,
|
|
45
|
+
full: 'n/a'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}, /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
49
|
+
offlineInterface: mockOfflineInterface
|
|
50
|
+
}, children));
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Assert on the delay of the last time setTimeoutSpy was called with
|
|
54
|
+
* the `callbackAndRestart()` function in smartInterval.
|
|
55
|
+
*
|
|
56
|
+
* This is useful because sometimes jest (or something) uses `setTimeout`
|
|
57
|
+
* too with a `_flushCallback` function, which gets in the way of using
|
|
58
|
+
* an assertion like:
|
|
59
|
+
* `expect(setTimeoutSpy).toHaveBeenLastCalledWith(..., expectedDelay)`
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
const assertLastDelay = (setTimeoutSpy, expectedDelay) => {
|
|
64
|
+
const calls = setTimeoutSpy.mock.calls;
|
|
65
|
+
|
|
66
|
+
for (let i = calls.length - 1; i >= 0; i--) {
|
|
67
|
+
if (calls[i][0].name === 'callbackAndRestart') {
|
|
68
|
+
expect(calls[i][1]).toBe(expectedDelay);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const testCurrentDate = new Date('Fri, 03 Feb 2023 13:52:31 GMT');
|
|
75
|
+
beforeAll(() => {
|
|
76
|
+
jest.useFakeTimers();
|
|
77
|
+
jest.spyOn(Date, 'now').mockReturnValue(testCurrentDate.getTime());
|
|
78
|
+
});
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
// standby state is initialized to window visibility, which is 'false' by
|
|
81
|
+
// default in tests. mock that here:
|
|
82
|
+
jest.spyOn(document, 'hasFocus').mockReturnValue(true);
|
|
83
|
+
});
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
jest.clearAllMocks(); // for lastConnected:
|
|
86
|
+
|
|
87
|
+
localStorage.clear();
|
|
88
|
+
});
|
|
89
|
+
afterAll(() => {
|
|
90
|
+
jest.useRealTimers();
|
|
91
|
+
jest.resetAllMocks();
|
|
92
|
+
});
|
|
93
|
+
describe('initialization to the right values based on offline interface', () => {
|
|
94
|
+
test('when latestIsConnected is true', () => {
|
|
95
|
+
const {
|
|
96
|
+
result
|
|
97
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
98
|
+
wrapper: wrapper
|
|
99
|
+
});
|
|
100
|
+
expect(result.current.isConnected).toBe(true);
|
|
101
|
+
expect(result.current.isDisconnected).toBe(false);
|
|
102
|
+
expect(result.current.lastConnected).toBe(null);
|
|
103
|
+
});
|
|
104
|
+
test('when latestIsConnected is false', () => {
|
|
105
|
+
const customMockOfflineInterface = { ...mockOfflineInterface,
|
|
106
|
+
latestIsConnected: false
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const customWrapper = (_ref2) => {
|
|
110
|
+
let {
|
|
111
|
+
children
|
|
112
|
+
} = _ref2;
|
|
113
|
+
return /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
114
|
+
offlineInterface: customMockOfflineInterface
|
|
115
|
+
}, children);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const {
|
|
119
|
+
result
|
|
120
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
121
|
+
wrapper: customWrapper
|
|
122
|
+
});
|
|
123
|
+
expect(result.current.isConnected).toBe(false);
|
|
124
|
+
expect(result.current.isDisconnected).toBe(true); // If localStorage is clear, sets 'lastConnected' to `now` as a best
|
|
125
|
+
// effort to provide useful information.
|
|
126
|
+
// There will be more detailed testing of lastConnected below
|
|
127
|
+
|
|
128
|
+
expect(result.current.lastConnected).toEqual(testCurrentDate);
|
|
129
|
+
}); // This might happen in the unlikely circumstance that the provider
|
|
130
|
+
// renders before the offlineInterface has received a value for
|
|
131
|
+
// lastIsConnected. Normally, the ServerVersionProvider in the app
|
|
132
|
+
// adapter delays rendering the App Runtime provider (including the
|
|
133
|
+
// OfflineProvider) until the offline interface is ready, which should
|
|
134
|
+
// avoid this case.
|
|
135
|
+
|
|
136
|
+
test('when latestIsConnected is null', () => {
|
|
137
|
+
const customMockOfflineInterface = { ...mockOfflineInterface,
|
|
138
|
+
latestIsConnected: null
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const customWrapper = (_ref3) => {
|
|
142
|
+
let {
|
|
143
|
+
children
|
|
144
|
+
} = _ref3;
|
|
145
|
+
return /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
146
|
+
offlineInterface: customMockOfflineInterface
|
|
147
|
+
}, children);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const {
|
|
151
|
+
result
|
|
152
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
153
|
+
wrapper: customWrapper
|
|
154
|
+
});
|
|
155
|
+
expect(result.current.isConnected).toBe(false);
|
|
156
|
+
expect(result.current.isDisconnected).toBe(true);
|
|
157
|
+
expect(result.current.lastConnected).toEqual(testCurrentDate);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe('interval behavior', () => {
|
|
161
|
+
test('the ping delay increases when idle until the max is reached', async () => {
|
|
162
|
+
const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
|
|
163
|
+
const {
|
|
164
|
+
result
|
|
165
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
166
|
+
wrapper: wrapper
|
|
167
|
+
});
|
|
168
|
+
expect(result.current.isConnected).toBe(true);
|
|
169
|
+
expect(mockPing).not.toHaveBeenCalled();
|
|
170
|
+
assertLastDelay(setTimeoutSpy, FIRST_INTERVAL_MS); // 500ms before first interval
|
|
171
|
+
|
|
172
|
+
jest.advanceTimersByTime(FIRST_INTERVAL_MS - 500);
|
|
173
|
+
expect(mockPing).not.toHaveBeenCalled(); // 500ms after first interval
|
|
174
|
+
|
|
175
|
+
jest.advanceTimersByTime(1000);
|
|
176
|
+
expect(mockPing).toHaveBeenCalledTimes(1);
|
|
177
|
+
assertLastDelay(setTimeoutSpy, SECOND_INTERVAL_MS); // 500ms before second interval
|
|
178
|
+
|
|
179
|
+
jest.advanceTimersByTime(SECOND_INTERVAL_MS - 1000);
|
|
180
|
+
expect(mockPing).toHaveBeenCalledTimes(1); // 500ms after second interval
|
|
181
|
+
|
|
182
|
+
jest.advanceTimersByTime(1000);
|
|
183
|
+
expect(mockPing).toHaveBeenCalledTimes(2);
|
|
184
|
+
assertLastDelay(setTimeoutSpy, THIRD_INTERVAL_MS); // 500ms before third interval
|
|
185
|
+
|
|
186
|
+
jest.advanceTimersByTime(THIRD_INTERVAL_MS - 1000);
|
|
187
|
+
expect(mockPing).toHaveBeenCalledTimes(2); // 500ms after third interval
|
|
188
|
+
|
|
189
|
+
jest.advanceTimersByTime(1000);
|
|
190
|
+
expect(mockPing).toHaveBeenCalledTimes(3);
|
|
191
|
+
assertLastDelay(setTimeoutSpy, FOURTH_INTERVAL_MS); // Run a number of intervals to reach the max delay -
|
|
192
|
+
// this number is calculated above to work for any default values.
|
|
193
|
+
// Since three have already elapsed, there will be some extra too
|
|
194
|
+
|
|
195
|
+
for (let i = 0; i < INTERVALS_TO_REACH_MAX_DELAY; i++) {
|
|
196
|
+
// Wrap in act to await async side effects of interval execution
|
|
197
|
+
// and pings
|
|
198
|
+
await act(async () => {
|
|
199
|
+
jest.runOnlyPendingTimers();
|
|
200
|
+
});
|
|
201
|
+
} // Timeout should no longer be incrementing; max has been reached
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
expect(mockPing).toHaveBeenCalledTimes(3 + INTERVALS_TO_REACH_MAX_DELAY);
|
|
205
|
+
assertLastDelay(setTimeoutSpy, DEFAULT_MAX_DELAY_MS); // Run a few more intervals to make sure it stays at max
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < 3; i++) {
|
|
208
|
+
await act(async () => {
|
|
209
|
+
jest.runOnlyPendingTimers();
|
|
210
|
+
});
|
|
211
|
+
} // Expect continued use of the max delay
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
expect(mockPing).toHaveBeenCalledTimes(6 + INTERVALS_TO_REACH_MAX_DELAY);
|
|
215
|
+
assertLastDelay(setTimeoutSpy, DEFAULT_MAX_DELAY_MS);
|
|
216
|
+
});
|
|
217
|
+
describe('pings are delayed when offlineInterface sends status updates', () => {
|
|
218
|
+
test('updates postpone pings', () => {
|
|
219
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
220
|
+
wrapper: wrapper
|
|
221
|
+
}); // get onUpdate function passed to mockOfflineInterface
|
|
222
|
+
|
|
223
|
+
const {
|
|
224
|
+
onUpdate
|
|
225
|
+
} = mockOfflineInterface.subscribeToDhis2ConnectionStatus.mock.calls[0][0]; // invoke it at a few intervals, before pings are scheduled
|
|
226
|
+
|
|
227
|
+
for (let i = 0; i < 3; i++) {
|
|
228
|
+
jest.advanceTimersByTime(DEFAULT_INITIAL_DELAY_MS - 2000);
|
|
229
|
+
onUpdate({
|
|
230
|
+
isConnected: true
|
|
231
|
+
});
|
|
232
|
+
} // expect ping mock not to have been called
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
expect(mockPing).not.toHaveBeenCalled;
|
|
236
|
+
});
|
|
237
|
+
test('if the status is the same, the ping delay is reset to the current', () => {
|
|
238
|
+
const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
|
|
239
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
240
|
+
wrapper
|
|
241
|
+
}); // get onUpdate function passed to mockOfflineInterface
|
|
242
|
+
|
|
243
|
+
const {
|
|
244
|
+
onUpdate
|
|
245
|
+
} = mockOfflineInterface.subscribeToDhis2ConnectionStatus.mock.calls[0][0]; // let two intervals pass to allow delay to increase
|
|
246
|
+
|
|
247
|
+
jest.advanceTimersByTime(FIRST_INTERVAL_MS + 50);
|
|
248
|
+
jest.advanceTimersByTime(SECOND_INTERVAL_MS); // ...delay should now be 'THIRD_INTERVAL_MS'
|
|
249
|
+
|
|
250
|
+
assertLastDelay(setTimeoutSpy, THIRD_INTERVAL_MS);
|
|
251
|
+
expect(mockPing).toHaveBeenCalledTimes(2); // simulate updates from the SW/offline interface several times
|
|
252
|
+
// invoke it at a few intervals, before pings are scheduled
|
|
253
|
+
|
|
254
|
+
for (let i = 0; i < 3; i++) {
|
|
255
|
+
jest.advanceTimersByTime(THIRD_INTERVAL_MS - 2000);
|
|
256
|
+
onUpdate({
|
|
257
|
+
isConnected: true
|
|
258
|
+
});
|
|
259
|
+
} // ping mock should STILL only have been called twice
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
expect(mockPing).toHaveBeenCalledTimes(2); // the delay should still be THIRD_INTERVAL_MS
|
|
263
|
+
|
|
264
|
+
assertLastDelay(setTimeoutSpy, THIRD_INTERVAL_MS); // The timer works as normal for the next tick --
|
|
265
|
+
// 500ms before the fourth interval:
|
|
266
|
+
|
|
267
|
+
jest.advanceTimersByTime(THIRD_INTERVAL_MS - 500);
|
|
268
|
+
expect(mockPing).toHaveBeenCalledTimes(2); // 500ms after the fourth interval
|
|
269
|
+
|
|
270
|
+
jest.advanceTimersByTime(1000);
|
|
271
|
+
expect(mockPing).toHaveBeenCalledTimes(3);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
describe('the ping interval resets to initial if the detected connection status changes', () => {
|
|
275
|
+
test('this happens when the offline interface issues an update', async () => {
|
|
276
|
+
const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
|
|
277
|
+
const {
|
|
278
|
+
result
|
|
279
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
280
|
+
wrapper: wrapper
|
|
281
|
+
}); // get onUpdate function passed to mockOfflineInterface
|
|
282
|
+
|
|
283
|
+
const {
|
|
284
|
+
onUpdate
|
|
285
|
+
} = mockOfflineInterface.subscribeToDhis2ConnectionStatus.mock.calls[0][0];
|
|
286
|
+
expect(result.current.isConnected).toBe(true); // Get to third interval
|
|
287
|
+
// (Wrap in `act` to await async side effects of the executions)
|
|
288
|
+
|
|
289
|
+
await act(async () => {
|
|
290
|
+
jest.runOnlyPendingTimers();
|
|
291
|
+
jest.runOnlyPendingTimers();
|
|
292
|
+
});
|
|
293
|
+
expect(mockPing).toHaveBeenCalledTimes(2);
|
|
294
|
+
assertLastDelay(setTimeoutSpy, THIRD_INTERVAL_MS); // Trigger connection status change from offline interface
|
|
295
|
+
|
|
296
|
+
await act(async () => {
|
|
297
|
+
onUpdate({
|
|
298
|
+
isConnected: false
|
|
299
|
+
});
|
|
300
|
+
}); // Expect "first interval delay" to be set up
|
|
301
|
+
|
|
302
|
+
assertLastDelay(setTimeoutSpy, FIRST_INTERVAL_MS);
|
|
303
|
+
expect(result.current.isConnected).toBe(false); // Mock an error for the next ping to maintain `isConnected: false`
|
|
304
|
+
|
|
305
|
+
mockPing.mockImplementationOnce(() => Promise.reject({
|
|
306
|
+
message: 'this is a network error',
|
|
307
|
+
type: 'network'
|
|
308
|
+
})); // Advance past "first interval" -- make sure incrementing resumes
|
|
309
|
+
// while still 'isConnected: false'
|
|
310
|
+
|
|
311
|
+
await act(async () => {
|
|
312
|
+
jest.advanceTimersByTime(FIRST_INTERVAL_MS + 50);
|
|
313
|
+
}); // Expect another execution with the incremented interval
|
|
314
|
+
|
|
315
|
+
expect(mockPing).toHaveBeenCalledTimes(3);
|
|
316
|
+
assertLastDelay(setTimeoutSpy, SECOND_INTERVAL_MS);
|
|
317
|
+
});
|
|
318
|
+
test('this happens if a ping detects a status change', async () => {
|
|
319
|
+
const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
|
|
320
|
+
const {
|
|
321
|
+
result
|
|
322
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
323
|
+
wrapper: wrapper
|
|
324
|
+
});
|
|
325
|
+
expect(result.current.isConnected).toBe(true); // Get to third interval
|
|
326
|
+
|
|
327
|
+
jest.runOnlyPendingTimers();
|
|
328
|
+
jest.runOnlyPendingTimers();
|
|
329
|
+
expect(mockPing).toHaveBeenCalledTimes(2);
|
|
330
|
+
assertLastDelay(setTimeoutSpy, THIRD_INTERVAL_MS); // Mock a network error
|
|
331
|
+
|
|
332
|
+
mockPing.mockImplementationOnce(() => Promise.reject({
|
|
333
|
+
message: 'this is a network error',
|
|
334
|
+
type: 'network'
|
|
335
|
+
}));
|
|
336
|
+
await act(async () => {
|
|
337
|
+
jest.advanceTimersByTime(THIRD_INTERVAL_MS + 50);
|
|
338
|
+
});
|
|
339
|
+
expect(result.current.isConnected).toBe(false);
|
|
340
|
+
expect(mockPing).toHaveBeenCalledTimes(3);
|
|
341
|
+
assertLastDelay(setTimeoutSpy, FIRST_INTERVAL_MS);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
describe('pings aren\'t sent when the app is not focused; "standby behavior"', () => {
|
|
346
|
+
test("it doesn't ping when the app loses focus and is never refocused", () => {
|
|
347
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
348
|
+
wrapper
|
|
349
|
+
});
|
|
350
|
+
window.dispatchEvent(new Event('blur')); // This recursively executes all timers -- if it's not in standby,
|
|
351
|
+
// it will enter a loop
|
|
352
|
+
|
|
353
|
+
jest.runAllTimers();
|
|
354
|
+
expect(mockPing).not.toHaveBeenCalled();
|
|
355
|
+
});
|
|
356
|
+
test("it doesn't ping if the app is never focused (even upon startup)", () => {
|
|
357
|
+
jest.spyOn(document, 'hasFocus').mockReturnValue(false);
|
|
358
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
359
|
+
wrapper
|
|
360
|
+
}); // This recursively executes all timers
|
|
361
|
+
|
|
362
|
+
jest.runAllTimers();
|
|
363
|
+
expect(mockPing).not.toHaveBeenCalled();
|
|
364
|
+
});
|
|
365
|
+
test('if the app is defocused and refocused between two pings, pings happen normally', () => {
|
|
366
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
367
|
+
wrapper
|
|
368
|
+
});
|
|
369
|
+
window.dispatchEvent(new Event('blur')); // wait half of the first interval
|
|
370
|
+
|
|
371
|
+
jest.advanceTimersByTime(FIRST_INTERVAL_MS / 2);
|
|
372
|
+
window.dispatchEvent(new Event('focus')); // wait for just over the second half of the first interval
|
|
373
|
+
|
|
374
|
+
jest.advanceTimersByTime(FIRST_INTERVAL_MS / 2 + 50); // ping should execute normally
|
|
375
|
+
|
|
376
|
+
expect(mockPing).toHaveBeenCalledTimes(1);
|
|
377
|
+
});
|
|
378
|
+
test('if the app is defocused until after a scheduled ping, that ping is not sent until the app is refocused', () => {
|
|
379
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
380
|
+
wrapper
|
|
381
|
+
});
|
|
382
|
+
window.dispatchEvent(new Event('blur')); // wait for twice the first interval
|
|
383
|
+
|
|
384
|
+
jest.advanceTimersByTime(FIRST_INTERVAL_MS * 2); // no pings should be sent since it's in standby
|
|
385
|
+
|
|
386
|
+
expect(mockPing).not.toHaveBeenCalled(); // refocus the page
|
|
387
|
+
|
|
388
|
+
window.dispatchEvent(new Event('focus')); // ping should execute immediately
|
|
389
|
+
|
|
390
|
+
expect(mockPing).toHaveBeenCalledTimes(1);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
describe('it pings when an offline event is detected', () => {
|
|
394
|
+
test('if the app is focused, it pings immediately', () => {
|
|
395
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
396
|
+
wrapper
|
|
397
|
+
});
|
|
398
|
+
window.dispatchEvent(new Event('offline')); // ping should execute immediately
|
|
399
|
+
|
|
400
|
+
expect(mockPing).toHaveBeenCalledTimes(1);
|
|
401
|
+
});
|
|
402
|
+
test('if the app is not focused, it does not ping immediately, but pings immediately when refocused', () => {
|
|
403
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
404
|
+
wrapper
|
|
405
|
+
});
|
|
406
|
+
window.dispatchEvent(new Event('blur'));
|
|
407
|
+
window.dispatchEvent(new Event('offline')); // ping should not execute, but should be queued for refocus
|
|
408
|
+
|
|
409
|
+
expect(mockPing).toHaveBeenCalledTimes(0); // upon refocus, the ping should execute immediately
|
|
410
|
+
// despite a full interval not elapsing
|
|
411
|
+
|
|
412
|
+
window.dispatchEvent(new Event('focus'));
|
|
413
|
+
expect(mockPing).toHaveBeenCalledTimes(1);
|
|
414
|
+
});
|
|
415
|
+
describe('interval handling when pinging upon refocusing after offline event is detected while not focused', () => {
|
|
416
|
+
test('if the app is refocused before the next "scheduled" ping, the timeout to the next ping is not increased', () => {
|
|
417
|
+
const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
|
|
418
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
419
|
+
wrapper
|
|
420
|
+
});
|
|
421
|
+
window.dispatchEvent(new Event('blur'));
|
|
422
|
+
window.dispatchEvent(new Event('offline'));
|
|
423
|
+
window.dispatchEvent(new Event('focus')); // upon refocus, the ping should execute immediately
|
|
424
|
+
// despite a full interval not elapsing
|
|
425
|
+
|
|
426
|
+
expect(mockPing).toHaveBeenCalledTimes(1); // The delay should be the initial again -- it shouldn't increment
|
|
427
|
+
|
|
428
|
+
assertLastDelay(setTimeoutSpy, FIRST_INTERVAL_MS);
|
|
429
|
+
});
|
|
430
|
+
test('same as previous, but interval is reset if status changes', async () => {
|
|
431
|
+
const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
|
|
432
|
+
const {
|
|
433
|
+
result
|
|
434
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
435
|
+
wrapper: wrapper
|
|
436
|
+
});
|
|
437
|
+
expect(result.current.isConnected).toBe(true); // Get to third interval
|
|
438
|
+
|
|
439
|
+
jest.runOnlyPendingTimers();
|
|
440
|
+
jest.runOnlyPendingTimers();
|
|
441
|
+
expect(mockPing).toHaveBeenCalledTimes(2);
|
|
442
|
+
assertLastDelay(setTimeoutSpy, THIRD_INTERVAL_MS); // Mock a network error
|
|
443
|
+
|
|
444
|
+
mockPing.mockImplementationOnce(() => Promise.reject({
|
|
445
|
+
message: 'this is a network error',
|
|
446
|
+
type: 'network'
|
|
447
|
+
})); // Blur, trigger 'offline' event, and refocus to trigger a ping
|
|
448
|
+
|
|
449
|
+
window.dispatchEvent(new Event('blur'));
|
|
450
|
+
window.dispatchEvent(new Event('offline'));
|
|
451
|
+
await act(async () => {
|
|
452
|
+
window.dispatchEvent(new Event('focus'));
|
|
453
|
+
});
|
|
454
|
+
expect(result.current.isConnected).toBe(false);
|
|
455
|
+
expect(mockPing).toHaveBeenCalledTimes(3);
|
|
456
|
+
assertLastDelay(setTimeoutSpy, FIRST_INTERVAL_MS);
|
|
457
|
+
});
|
|
458
|
+
test('if the app is refocused after the next "scheduled" ping, increase the interval to the next ping if the status hasn\'t changed', () => {
|
|
459
|
+
const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
|
|
460
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
461
|
+
wrapper
|
|
462
|
+
});
|
|
463
|
+
window.dispatchEvent(new Event('blur'));
|
|
464
|
+
window.dispatchEvent(new Event('offline')); // Elapse twice one interval - it should enter full standby
|
|
465
|
+
|
|
466
|
+
jest.advanceTimersByTime(FIRST_INTERVAL_MS * 2);
|
|
467
|
+
expect(mockPing).toHaveBeenCalledTimes(0); // Refocusing should trigger a ping from the full standby,
|
|
468
|
+
// not just the offline event
|
|
469
|
+
|
|
470
|
+
window.dispatchEvent(new Event('focus'));
|
|
471
|
+
expect(mockPing).toHaveBeenCalledTimes(1); // The delay should increment this time, as it would from normal standby
|
|
472
|
+
|
|
473
|
+
assertLastDelay(setTimeoutSpy, SECOND_INTERVAL_MS);
|
|
474
|
+
});
|
|
475
|
+
test('the same as previous, but the interval is reset if status has changed', async () => {
|
|
476
|
+
const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
|
|
477
|
+
const {
|
|
478
|
+
result
|
|
479
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
480
|
+
wrapper: wrapper
|
|
481
|
+
});
|
|
482
|
+
expect(result.current.isConnected).toBe(true); // Get to third interval
|
|
483
|
+
|
|
484
|
+
jest.runOnlyPendingTimers();
|
|
485
|
+
jest.runOnlyPendingTimers();
|
|
486
|
+
expect(mockPing).toHaveBeenCalledTimes(2);
|
|
487
|
+
assertLastDelay(setTimeoutSpy, THIRD_INTERVAL_MS); // Blur and elapse twice the third interval --
|
|
488
|
+
// it should enter full standby
|
|
489
|
+
|
|
490
|
+
window.dispatchEvent(new Event('blur'));
|
|
491
|
+
window.dispatchEvent(new Event('offline'));
|
|
492
|
+
jest.advanceTimersByTime(THIRD_INTERVAL_MS * 2); // Mock a network error for the next ping
|
|
493
|
+
|
|
494
|
+
mockPing.mockImplementationOnce(() => Promise.reject({
|
|
495
|
+
message: 'this is a network error',
|
|
496
|
+
type: 'network'
|
|
497
|
+
})); // Trigger a ping by refocusing
|
|
498
|
+
|
|
499
|
+
await act(async () => {
|
|
500
|
+
window.dispatchEvent(new Event('focus'));
|
|
501
|
+
});
|
|
502
|
+
expect(result.current.isConnected).toBe(false);
|
|
503
|
+
expect(mockPing).toHaveBeenCalledTimes(3);
|
|
504
|
+
assertLastDelay(setTimeoutSpy, FIRST_INTERVAL_MS);
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
describe('lastConnected status', () => {
|
|
509
|
+
test('it sets lastConnected in localStorage when it becomes disconnected', async () => {
|
|
510
|
+
const {
|
|
511
|
+
result
|
|
512
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
513
|
+
wrapper: wrapper
|
|
514
|
+
});
|
|
515
|
+
expect(result.current.isConnected).toBe(true); // Mock a network error for the next ping
|
|
516
|
+
|
|
517
|
+
mockPing.mockImplementationOnce(failedPing); // Trigger a ping (to fail and switch to disconnected)
|
|
518
|
+
|
|
519
|
+
await act(async () => {
|
|
520
|
+
jest.runOnlyPendingTimers();
|
|
521
|
+
});
|
|
522
|
+
expect(mockPing).toHaveBeenCalledTimes(1); // Expect 'disconnected' status now
|
|
523
|
+
|
|
524
|
+
expect(result.current.isConnected).toBe(false);
|
|
525
|
+
expect(result.current.isDisconnected).toBe(true); // Check localStorage for the dummy date
|
|
526
|
+
|
|
527
|
+
const localStorageDate = localStorage.getItem(getLastConnectedKey());
|
|
528
|
+
expect(localStorageDate).toBe(testCurrentDate.toUTCString()); // Check hook return value
|
|
529
|
+
|
|
530
|
+
expect(result.current.lastConnected).toBeInstanceOf(Date);
|
|
531
|
+
expect(result.current.lastConnected).toEqual(testCurrentDate);
|
|
532
|
+
});
|
|
533
|
+
test('lastConnected becomes null when it becomes connected again', async () => {
|
|
534
|
+
const {
|
|
535
|
+
result
|
|
536
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
537
|
+
wrapper: wrapper
|
|
538
|
+
});
|
|
539
|
+
expect(result.current.isConnected).toBe(true); // Mock a network error for the next ping
|
|
540
|
+
|
|
541
|
+
mockPing.mockImplementationOnce(failedPing); // Trigger an immediate ping (to fail and switch to disconnected)
|
|
542
|
+
|
|
543
|
+
await act(async () => {
|
|
544
|
+
jest.runOnlyPendingTimers();
|
|
545
|
+
});
|
|
546
|
+
expect(mockPing).toHaveBeenCalledTimes(1); // Verify hook return value
|
|
547
|
+
|
|
548
|
+
expect(result.current.isConnected).toBe(false);
|
|
549
|
+
expect(result.current.lastConnected).toEqual(testCurrentDate); // Trigger a successful ping to go back online
|
|
550
|
+
|
|
551
|
+
await act(async () => {
|
|
552
|
+
jest.runOnlyPendingTimers();
|
|
553
|
+
});
|
|
554
|
+
expect(mockPing).toHaveBeenCalledTimes(2);
|
|
555
|
+
expect(result.current.isConnected).toBe(true);
|
|
556
|
+
expect(result.current.lastConnected).toBe(null);
|
|
557
|
+
});
|
|
558
|
+
test('lastConnected persists in localStorage if unmounted while disconnected', async () => {
|
|
559
|
+
const {
|
|
560
|
+
result,
|
|
561
|
+
unmount
|
|
562
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
563
|
+
wrapper: wrapper
|
|
564
|
+
}); // Mock a network error for the next ping to trigger 'disconnected'
|
|
565
|
+
|
|
566
|
+
mockPing.mockImplementationOnce(failedPing);
|
|
567
|
+
await act(async () => {
|
|
568
|
+
jest.runOnlyPendingTimers();
|
|
569
|
+
});
|
|
570
|
+
expect(result.current.isConnected).toBe(false); // Unmount
|
|
571
|
+
|
|
572
|
+
unmount(); // Expect value to persist in localStorage
|
|
573
|
+
|
|
574
|
+
const localStorageDate = localStorage.getItem(getLastConnectedKey());
|
|
575
|
+
expect(localStorageDate).toBe(testCurrentDate.toUTCString());
|
|
576
|
+
});
|
|
577
|
+
test('lastConnected is cleared from localStorage after unmounting while connected', async () => {
|
|
578
|
+
const {
|
|
579
|
+
result,
|
|
580
|
+
unmount
|
|
581
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
582
|
+
wrapper
|
|
583
|
+
});
|
|
584
|
+
expect(result.current.isConnected).toBe(true); // Mock a network error for the next ping to trigger disconnected
|
|
585
|
+
|
|
586
|
+
mockPing.mockImplementationOnce(failedPing);
|
|
587
|
+
await act(async () => {
|
|
588
|
+
jest.runOnlyPendingTimers();
|
|
589
|
+
});
|
|
590
|
+
expect(result.current.isConnected).toBe(false); // Check localStorage for the dummy date
|
|
591
|
+
|
|
592
|
+
const localStorageDate = localStorage.getItem(getLastConnectedKey());
|
|
593
|
+
expect(localStorageDate).toBe(testCurrentDate.toUTCString()); // Trigger another ping to go back to connected
|
|
594
|
+
|
|
595
|
+
await act(async () => {
|
|
596
|
+
jest.runOnlyPendingTimers();
|
|
597
|
+
});
|
|
598
|
+
expect(result.current.isConnected).toBe(true); // Unmount and expect localStorage to be clear for next session
|
|
599
|
+
|
|
600
|
+
unmount();
|
|
601
|
+
expect(localStorage.getItem(getLastConnectedKey())).toBe(null);
|
|
602
|
+
});
|
|
603
|
+
describe('starting while disconnected', () => {
|
|
604
|
+
test('it sets lastConnected to `now` if nothing is found in localStorage', async () => {
|
|
605
|
+
// use a custom offlineInterface with `latestIsConnected: false`
|
|
606
|
+
// to initialize the `isConnected` state to false
|
|
607
|
+
const customMockOfflineInterface = { ...mockOfflineInterface,
|
|
608
|
+
latestIsConnected: false
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
const customWrapper = (_ref4) => {
|
|
612
|
+
let {
|
|
613
|
+
children
|
|
614
|
+
} = _ref4;
|
|
615
|
+
return /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
616
|
+
offlineInterface: customMockOfflineInterface
|
|
617
|
+
}, children);
|
|
618
|
+
}; // render hook with custom wrapper
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
622
|
+
wrapper: customWrapper
|
|
623
|
+
}); // expect correct lastConnected time (mocked Date.now())
|
|
624
|
+
|
|
625
|
+
expect(localStorage.getItem(getLastConnectedKey())).toBe(testCurrentDate.toUTCString());
|
|
626
|
+
});
|
|
627
|
+
test('if a value is already in localStorage, it uses that without overwriting', async () => {
|
|
628
|
+
// seed localStorage with an imaginary 'lastConnected' value from last session
|
|
629
|
+
const testPreviousDate = new Date('2023-01-01');
|
|
630
|
+
localStorage.setItem(getLastConnectedKey(), testPreviousDate.toUTCString()); // render hook with custom wrapper
|
|
631
|
+
|
|
632
|
+
const customMockOfflineInterface = { ...mockOfflineInterface,
|
|
633
|
+
latestIsConnected: false
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const customWrapper = (_ref5) => {
|
|
637
|
+
let {
|
|
638
|
+
children
|
|
639
|
+
} = _ref5;
|
|
640
|
+
return /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
641
|
+
offlineInterface: customMockOfflineInterface
|
|
642
|
+
}, children);
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const {
|
|
646
|
+
result
|
|
647
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
648
|
+
wrapper: customWrapper
|
|
649
|
+
}); // On render, the hook should retain last connected
|
|
650
|
+
|
|
651
|
+
expect(result.current.lastConnected).not.toBe(null);
|
|
652
|
+
expect(result.current.lastConnected).toEqual(testPreviousDate); // should be the same in localStorage too
|
|
653
|
+
|
|
654
|
+
expect(localStorage.getItem(getLastConnectedKey())).toBe(testPreviousDate.toUTCString());
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
test("it doesn't change lastConnected if already disconnected", async () => {
|
|
658
|
+
// seed localStorage with an imaginary 'lastConnected' value from last session
|
|
659
|
+
const testPreviousDate = new Date('2023-01-01');
|
|
660
|
+
localStorage.setItem(getLastConnectedKey(), testPreviousDate.toUTCString()); // render hook with custom wrapper
|
|
661
|
+
|
|
662
|
+
const customMockOfflineInterface = { ...mockOfflineInterface,
|
|
663
|
+
latestIsConnected: false
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const customWrapper = (_ref6) => {
|
|
667
|
+
let {
|
|
668
|
+
children
|
|
669
|
+
} = _ref6;
|
|
670
|
+
return /*#__PURE__*/React.createElement(ConfigProvider, {
|
|
671
|
+
config: {
|
|
672
|
+
baseUrl: '..',
|
|
673
|
+
apiVersion: 42,
|
|
674
|
+
serverVersion: {
|
|
675
|
+
major: 2,
|
|
676
|
+
minor: 40,
|
|
677
|
+
patch: 0,
|
|
678
|
+
full: 'n/a'
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}, /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
682
|
+
offlineInterface: customMockOfflineInterface
|
|
683
|
+
}, children));
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const {
|
|
687
|
+
result
|
|
688
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
689
|
+
wrapper: customWrapper
|
|
690
|
+
}); // As in previous test, the hook should retain last connected
|
|
691
|
+
|
|
692
|
+
expect(result.current.lastConnected).toEqual(testPreviousDate); // Mock a network error for the next ping and trigger
|
|
693
|
+
|
|
694
|
+
mockPing.mockImplementationOnce(failedPing);
|
|
695
|
+
await act(async () => {
|
|
696
|
+
jest.runOnlyPendingTimers();
|
|
697
|
+
});
|
|
698
|
+
expect(mockPing).toHaveBeenCalledTimes(1); // Expect the same lastConnected as before
|
|
699
|
+
|
|
700
|
+
expect(result.current.lastConnected).toEqual(testPreviousDate); // should be the same in localStorage too
|
|
701
|
+
|
|
702
|
+
expect(localStorage.getItem(getLastConnectedKey())).toBe(testPreviousDate.toUTCString()); // Verify the same with a signal from the service worker
|
|
703
|
+
// get onUpdate function passed to mockOfflineInterface
|
|
704
|
+
|
|
705
|
+
const {
|
|
706
|
+
onUpdate
|
|
707
|
+
} = mockOfflineInterface.subscribeToDhis2ConnectionStatus.mock.calls[0][0];
|
|
708
|
+
await act(async () => {
|
|
709
|
+
onUpdate({
|
|
710
|
+
isConnected: false
|
|
711
|
+
});
|
|
712
|
+
}); // Expect the same lastConnected as before
|
|
713
|
+
|
|
714
|
+
expect(result.current.lastConnected).toEqual(testPreviousDate);
|
|
715
|
+
});
|
|
716
|
+
test('lastConnected is saved specifically to an app if a name is provided', async () => {
|
|
717
|
+
// seed localStorage with an imaginary 'lastConnected' value from last session
|
|
718
|
+
const testAppName = 'test-app-name';
|
|
719
|
+
const lastConnectedKey = getLastConnectedKey(testAppName);
|
|
720
|
+
const testPreviousDate = new Date('2023-01-01');
|
|
721
|
+
localStorage.setItem(lastConnectedKey, testPreviousDate.toUTCString()); // render hook with custom wrapper to start disconnected with app name
|
|
722
|
+
|
|
723
|
+
const customMockOfflineInterface = { ...mockOfflineInterface,
|
|
724
|
+
latestIsConnected: false
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const customWrapper = (_ref7) => {
|
|
728
|
+
let {
|
|
729
|
+
children
|
|
730
|
+
} = _ref7;
|
|
731
|
+
return /*#__PURE__*/React.createElement(ConfigProvider, {
|
|
732
|
+
config: {
|
|
733
|
+
baseUrl: '..',
|
|
734
|
+
apiVersion: 42,
|
|
735
|
+
appName: testAppName,
|
|
736
|
+
serverVersion: {
|
|
737
|
+
major: 2,
|
|
738
|
+
minor: 40,
|
|
739
|
+
patch: 0,
|
|
740
|
+
full: 'n/a'
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}, /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
744
|
+
offlineInterface: customMockOfflineInterface
|
|
745
|
+
}, children));
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
const {
|
|
749
|
+
result
|
|
750
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
751
|
+
wrapper: customWrapper
|
|
752
|
+
}); // Expect previous value to be read correctly
|
|
753
|
+
|
|
754
|
+
expect(result.current.lastConnected).toEqual(testPreviousDate); // Go to connected then disconnected again to generate a new date
|
|
755
|
+
|
|
756
|
+
await act(async () => {
|
|
757
|
+
jest.runOnlyPendingTimers();
|
|
758
|
+
});
|
|
759
|
+
expect(result.current.isConnected).toBe(true);
|
|
760
|
+
expect(result.current.lastConnected).toBe(null);
|
|
761
|
+
expect(localStorage.getItem(lastConnectedKey)).toBe(null);
|
|
762
|
+
mockPing.mockImplementationOnce(failedPing);
|
|
763
|
+
await act(async () => {
|
|
764
|
+
jest.runOnlyPendingTimers();
|
|
765
|
+
});
|
|
766
|
+
expect(result.current.isConnected).toBe(false); // Note the new date:
|
|
767
|
+
|
|
768
|
+
expect(result.current.lastConnected).toEqual(testCurrentDate); // Verify localStorage
|
|
769
|
+
|
|
770
|
+
expect(localStorage.getItem(lastConnectedKey)).toBe(testCurrentDate.toUTCString());
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
describe("when the /api/ping endpoint isn't supported", () => {
|
|
774
|
+
const customWrapper = (_ref8) => {
|
|
775
|
+
let {
|
|
776
|
+
children
|
|
777
|
+
} = _ref8;
|
|
778
|
+
return /*#__PURE__*/React.createElement(ConfigProvider, {
|
|
779
|
+
config: {
|
|
780
|
+
baseUrl: '..',
|
|
781
|
+
apiVersion: 42,
|
|
782
|
+
// an unsupported version:
|
|
783
|
+
serverVersion: {
|
|
784
|
+
major: 2,
|
|
785
|
+
minor: 39,
|
|
786
|
+
patch: 0,
|
|
787
|
+
full: 'n/a'
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}, /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
791
|
+
offlineInterface: mockOfflineInterface
|
|
792
|
+
}, children));
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
test("pings aren't sent", async () => {
|
|
796
|
+
const setTimeoutSpy = jest.spyOn(window, 'setTimeout');
|
|
797
|
+
renderHook(() => useDhis2ConnectionStatus(), {
|
|
798
|
+
wrapper: customWrapper
|
|
799
|
+
});
|
|
800
|
+
await act(async () => {
|
|
801
|
+
jest.runAllTimers();
|
|
802
|
+
});
|
|
803
|
+
expect(mockPing).not.toHaveBeenCalled();
|
|
804
|
+
expect(setTimeoutSpy).not.toHaveBeenCalled();
|
|
805
|
+
});
|
|
806
|
+
test('service worker updates still work', async () => {
|
|
807
|
+
const {
|
|
808
|
+
result
|
|
809
|
+
} = renderHook(() => useDhis2ConnectionStatus(), {
|
|
810
|
+
wrapper: wrapper
|
|
811
|
+
}); // get onUpdate function passed to mockOfflineInterface
|
|
812
|
+
|
|
813
|
+
const {
|
|
814
|
+
onUpdate
|
|
815
|
+
} = mockOfflineInterface.subscribeToDhis2ConnectionStatus.mock.calls[0][0];
|
|
816
|
+
expect(result.current.isConnected).toBe(true); // Trigger connection status change from offline interface
|
|
817
|
+
|
|
818
|
+
await act(async () => {
|
|
819
|
+
onUpdate({
|
|
820
|
+
isConnected: false
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
expect(result.current.isConnected).toBe(false);
|
|
824
|
+
await act(async () => {
|
|
825
|
+
onUpdate({
|
|
826
|
+
isConnected: true
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
expect(result.current.isConnected).toBe(true);
|
|
830
|
+
});
|
|
831
|
+
});
|