@dhis2/app-service-offline 2.10.0 → 2.12.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 +337 -0
- package/build/cjs/index.js +39 -1
- package/build/cjs/lib/__tests__/clear-sensitive-caches.test.js +131 -0
- package/build/cjs/lib/__tests__/offline-provider.test.js +127 -0
- package/build/cjs/lib/__tests__/use-cacheable-section.test.js +227 -0
- package/build/cjs/lib/cacheable-section-state.js +218 -0
- package/build/cjs/lib/cacheable-section.js +156 -0
- package/build/cjs/lib/clear-sensitive-caches.js +87 -0
- package/build/cjs/lib/global-state-service.js +95 -0
- package/build/cjs/lib/offline-interface.js +86 -0
- package/build/cjs/lib/offline-provider.js +53 -0
- package/build/cjs/types.js +0 -1
- package/build/cjs/utils/__tests__/render-counter.test.js +55 -0
- package/build/cjs/utils/render-counter.js +26 -0
- package/build/cjs/utils/test-mocks.js +40 -0
- package/build/es/__tests__/integration.test.js +327 -0
- package/build/es/index.js +5 -1
- package/build/es/lib/__tests__/clear-sensitive-caches.test.js +123 -0
- package/build/es/lib/__tests__/offline-provider.test.js +117 -0
- package/build/es/lib/__tests__/use-cacheable-section.test.js +218 -0
- package/build/es/lib/cacheable-section-state.js +199 -0
- package/build/es/lib/cacheable-section.js +137 -0
- package/build/es/lib/clear-sensitive-caches.js +78 -0
- package/build/es/lib/global-state-service.js +70 -0
- package/build/es/lib/offline-interface.js +65 -0
- package/build/es/lib/offline-provider.js +40 -0
- package/build/es/types.js +0 -1
- package/build/es/utils/__tests__/render-counter.test.js +40 -0
- package/build/es/utils/render-counter.js +11 -0
- package/build/es/utils/test-mocks.js +30 -0
- package/build/types/index.d.ts +4 -0
- package/build/types/lib/cacheable-section-state.d.ts +66 -0
- package/build/types/lib/cacheable-section.d.ts +52 -0
- package/build/types/lib/clear-sensitive-caches.d.ts +16 -0
- package/build/types/lib/global-state-service.d.ts +16 -0
- package/build/types/lib/offline-interface.d.ts +26 -0
- package/build/types/lib/offline-provider.d.ts +19 -0
- package/build/types/types.d.ts +50 -0
- package/build/types/utils/render-counter.d.ts +10 -0
- package/build/types/utils/test-mocks.d.ts +11 -0
- package/package.json +2 -2
package/build/es/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { OfflineProvider } from './lib/offline-provider';
|
|
2
|
+
export { CacheableSection, useCacheableSection } from './lib/cacheable-section';
|
|
3
|
+
export { useCachedSections } from './lib/cacheable-section-state';
|
|
4
|
+
export { useOnlineStatus } from './lib/online-status';
|
|
5
|
+
export { clearSensitiveCaches } from './lib/clear-sensitive-caches';
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import FDBFactory from 'fake-indexeddb/lib/FDBFactory';
|
|
2
|
+
import { openDB } from 'idb';
|
|
3
|
+
import 'fake-indexeddb/auto';
|
|
4
|
+
import { clearSensitiveCaches, SECTIONS_DB, SECTIONS_STORE } from '../clear-sensitive-caches'; // Mocks for CacheStorage API
|
|
5
|
+
|
|
6
|
+
const keysMockDefault = jest.fn().mockImplementation(async () => []);
|
|
7
|
+
const deleteMockDefault = jest.fn().mockImplementation(async () => null);
|
|
8
|
+
const cachesDefault = {
|
|
9
|
+
keys: keysMockDefault,
|
|
10
|
+
delete: deleteMockDefault
|
|
11
|
+
};
|
|
12
|
+
window.caches = cachesDefault;
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
window.caches = cachesDefault;
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
}); // silence debug logs for these tests
|
|
17
|
+
|
|
18
|
+
const originalDebug = console.debug;
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
jest.spyOn(console, 'debug').mockImplementation((...args) => {
|
|
21
|
+
const pattern = /Clearing sensitive caches/;
|
|
22
|
+
|
|
23
|
+
if (typeof args[0] === 'string' && pattern.test(args[0])) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return originalDebug.call(console, ...args);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
afterAll(() => {
|
|
31
|
+
;
|
|
32
|
+
console.debug.mockRestore();
|
|
33
|
+
});
|
|
34
|
+
it('does not fail if there are no caches or no sections-db', () => {
|
|
35
|
+
return expect(clearSensitiveCaches()).resolves.toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
it('clears potentially sensitive caches', async () => {
|
|
38
|
+
const keysMock = jest.fn().mockImplementation(async () => ['cache1', 'cache2', 'app-shell']);
|
|
39
|
+
window.caches = { ...cachesDefault,
|
|
40
|
+
keys: keysMock
|
|
41
|
+
};
|
|
42
|
+
await clearSensitiveCaches();
|
|
43
|
+
expect(deleteMockDefault).toHaveBeenCalledTimes(3);
|
|
44
|
+
expect(deleteMockDefault.mock.calls[0][0]).toBe('cache1');
|
|
45
|
+
expect(deleteMockDefault.mock.calls[1][0]).toBe('cache2');
|
|
46
|
+
expect(deleteMockDefault.mock.calls[2][0]).toBe('app-shell');
|
|
47
|
+
});
|
|
48
|
+
it('preserves keepable caches', async () => {
|
|
49
|
+
const keysMock = jest.fn().mockImplementation(async () => ['cache1', 'cache2', 'app-shell', 'other-assets', 'workbox-precache-v2-https://hey.howareya.now/']);
|
|
50
|
+
window.caches = { ...cachesDefault,
|
|
51
|
+
keys: keysMock
|
|
52
|
+
};
|
|
53
|
+
await clearSensitiveCaches();
|
|
54
|
+
expect(deleteMockDefault).toHaveBeenCalledTimes(3);
|
|
55
|
+
expect(deleteMockDefault.mock.calls[0][0]).toBe('cache1');
|
|
56
|
+
expect(deleteMockDefault.mock.calls[1][0]).toBe('cache2');
|
|
57
|
+
expect(deleteMockDefault.mock.calls[2][0]).toBe('app-shell');
|
|
58
|
+
expect(deleteMockDefault).not.toHaveBeenCalledWith('other-assets');
|
|
59
|
+
expect(deleteMockDefault).not.toHaveBeenCalledWith('workbox-precache-v2-https://hey.howareya.now/');
|
|
60
|
+
});
|
|
61
|
+
describe('clears sections-db', () => {
|
|
62
|
+
// Test DB
|
|
63
|
+
function openTestDB(dbName) {
|
|
64
|
+
// simplified version of app platform openDB logic
|
|
65
|
+
return openDB(dbName, 1, {
|
|
66
|
+
upgrade(db) {
|
|
67
|
+
db.createObjectStore(SECTIONS_STORE, {
|
|
68
|
+
keyPath: 'sectionId'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
// reset indexedDB state
|
|
77
|
+
window.indexedDB = new FDBFactory();
|
|
78
|
+
});
|
|
79
|
+
it('clears sections-db if it exists', async () => {
|
|
80
|
+
// Open and populate test DB
|
|
81
|
+
const db = await openTestDB(SECTIONS_DB);
|
|
82
|
+
await db.put(SECTIONS_STORE, {
|
|
83
|
+
sectionId: 'id-1',
|
|
84
|
+
lastUpdated: new Date(),
|
|
85
|
+
requests: 3
|
|
86
|
+
});
|
|
87
|
+
await db.put(SECTIONS_STORE, {
|
|
88
|
+
sectionId: 'id-2',
|
|
89
|
+
lastUpdated: new Date(),
|
|
90
|
+
requests: 3
|
|
91
|
+
});
|
|
92
|
+
await clearSensitiveCaches(); // Sections-db should be cleared
|
|
93
|
+
|
|
94
|
+
const allSections = await db.getAll(SECTIONS_STORE);
|
|
95
|
+
expect(allSections).toHaveLength(0);
|
|
96
|
+
});
|
|
97
|
+
it("doesn't clear sections-db if it doesn't exist and doesn't open a new one", async () => {
|
|
98
|
+
const openMock = jest.fn();
|
|
99
|
+
window.indexedDB.open = openMock;
|
|
100
|
+
expect(await indexedDB.databases()).not.toContain(SECTIONS_DB);
|
|
101
|
+
await clearSensitiveCaches();
|
|
102
|
+
expect(openMock).not.toHaveBeenCalled();
|
|
103
|
+
return expect(await indexedDB.databases()).not.toContain(SECTIONS_DB);
|
|
104
|
+
});
|
|
105
|
+
it("doesn't handle IDB if 'databases' property is not on window.indexedDB", async () => {
|
|
106
|
+
// Open DB -- 'indexedDB.open' _would_ get called in this test
|
|
107
|
+
// if 'databases' property exists
|
|
108
|
+
await openTestDB(SECTIONS_DB);
|
|
109
|
+
const openMock = jest.fn();
|
|
110
|
+
window.indexedDB.open = openMock; // Remove 'databases' from indexedDB prototype for this test
|
|
111
|
+
// (simulates Firefox environment)
|
|
112
|
+
|
|
113
|
+
const idbProto = Object.getPrototypeOf(window.indexedDB);
|
|
114
|
+
const databases = idbProto.databases;
|
|
115
|
+
delete idbProto.databases;
|
|
116
|
+
expect('databases' in window.indexedDB).toBe(false);
|
|
117
|
+
await expect(clearSensitiveCaches()).resolves.toBeDefined();
|
|
118
|
+
expect(openMock).not.toHaveBeenCalled(); // Restore indexedDB prototype for later tests
|
|
119
|
+
|
|
120
|
+
idbProto.databases = databases;
|
|
121
|
+
expect('databases' in window.indexedDB).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { mockOfflineInterface } from '../../utils/test-mocks';
|
|
4
|
+
import { useCacheableSection, CacheableSection } from '../cacheable-section';
|
|
5
|
+
import { useCachedSections } from '../cacheable-section-state';
|
|
6
|
+
import { OfflineProvider } from '../offline-provider'; // Suppress 'act' warning for these tests
|
|
7
|
+
|
|
8
|
+
const originalError = console.error;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.spyOn(console, 'error').mockImplementation((...args) => {
|
|
11
|
+
const pattern = /Warning: An update to .* inside a test was not wrapped in act/;
|
|
12
|
+
|
|
13
|
+
if (typeof args[0] === 'string' && pattern.test(args[0])) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return originalError.call(console, ...args);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
jest.clearAllMocks() // syntax needed to appease typescript
|
|
22
|
+
;
|
|
23
|
+
console.error.mockRestore();
|
|
24
|
+
});
|
|
25
|
+
describe('Testing offline provider', () => {
|
|
26
|
+
it('Should render without failing', () => {
|
|
27
|
+
render( /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
28
|
+
offlineInterface: mockOfflineInterface
|
|
29
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
30
|
+
"data-testid": "test-div"
|
|
31
|
+
})));
|
|
32
|
+
expect(screen.getByTestId('test-div')).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
it('Should initialize the offline interface with an update prompt', () => {
|
|
35
|
+
render( /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
36
|
+
offlineInterface: mockOfflineInterface
|
|
37
|
+
}));
|
|
38
|
+
expect(mockOfflineInterface.init).toHaveBeenCalledTimes(1); // Expect to have been called with a 'promptUpdate' function
|
|
39
|
+
|
|
40
|
+
const arg = mockOfflineInterface.init.mock.calls[0][0];
|
|
41
|
+
expect(arg).toHaveProperty('promptUpdate');
|
|
42
|
+
expect(typeof arg['promptUpdate']).toBe('function');
|
|
43
|
+
});
|
|
44
|
+
it('Should sync cached sections with indexedDB', async () => {
|
|
45
|
+
const testOfflineInterface = { ...mockOfflineInterface,
|
|
46
|
+
getCachedSections: jest.fn().mockResolvedValue([{
|
|
47
|
+
sectionId: '1',
|
|
48
|
+
lastUpdated: 'date1'
|
|
49
|
+
}, {
|
|
50
|
+
sectionId: '2',
|
|
51
|
+
lastUpdated: 'date2'
|
|
52
|
+
}])
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const CachedSections = () => {
|
|
56
|
+
const {
|
|
57
|
+
cachedSections
|
|
58
|
+
} = useCachedSections();
|
|
59
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
60
|
+
"data-testid": "sections"
|
|
61
|
+
}, JSON.stringify(cachedSections));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
render( /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
65
|
+
offlineInterface: testOfflineInterface
|
|
66
|
+
}, /*#__PURE__*/React.createElement(CachedSections, null)));
|
|
67
|
+
const {
|
|
68
|
+
getByTestId
|
|
69
|
+
} = screen;
|
|
70
|
+
expect(testOfflineInterface.getCachedSections).toHaveBeenCalled();
|
|
71
|
+
await waitFor(() => getByTestId('sections').textContent !== '{}');
|
|
72
|
+
const textContent = JSON.parse(getByTestId('sections').textContent || '');
|
|
73
|
+
expect(textContent).toEqual({
|
|
74
|
+
1: {
|
|
75
|
+
lastUpdated: 'date1'
|
|
76
|
+
},
|
|
77
|
+
2: {
|
|
78
|
+
lastUpdated: 'date2'
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
it('Should provide the relevant contexts to consumers', () => {
|
|
83
|
+
const TestConsumer = () => {
|
|
84
|
+
useCacheableSection('id');
|
|
85
|
+
return /*#__PURE__*/React.createElement(CacheableSection, {
|
|
86
|
+
loadingMask: /*#__PURE__*/React.createElement("div", null),
|
|
87
|
+
id: 'id'
|
|
88
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
89
|
+
"data-testid": "test-div"
|
|
90
|
+
}));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
render( /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
94
|
+
offlineInterface: mockOfflineInterface
|
|
95
|
+
}, /*#__PURE__*/React.createElement(TestConsumer, null)));
|
|
96
|
+
expect(screen.getByTestId('test-div')).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
it('Should render without failing when no offlineInterface is provided', () => {
|
|
99
|
+
render( /*#__PURE__*/React.createElement(OfflineProvider, null, /*#__PURE__*/React.createElement("div", {
|
|
100
|
+
"data-testid": "test-div"
|
|
101
|
+
})));
|
|
102
|
+
expect(screen.getByTestId('test-div')).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
it('Should render without failing if PWA is not enabled', () => {
|
|
105
|
+
const testOfflineInterface = { ...mockOfflineInterface,
|
|
106
|
+
pwaEnabled: false
|
|
107
|
+
};
|
|
108
|
+
render( /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
109
|
+
offlineInterface: testOfflineInterface
|
|
110
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
111
|
+
"data-testid": "test-div"
|
|
112
|
+
}))); // Init should still be called - see comments in offline-provider.js
|
|
113
|
+
|
|
114
|
+
expect(testOfflineInterface.init).toHaveBeenCalled();
|
|
115
|
+
expect(screen.getByTestId('test-div')).toBeInTheDocument();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/* eslint-disable react/display-name, react/prop-types */
|
|
2
|
+
import { renderHook, act } from '@testing-library/react-hooks';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { errorRecordingMock, failedMessageRecordingMock, mockOfflineInterface } from '../../utils/test-mocks';
|
|
5
|
+
import { useCacheableSection } from '../cacheable-section';
|
|
6
|
+
import { OfflineProvider } from '../offline-provider'; // Suppress 'act' warning for these tests
|
|
7
|
+
|
|
8
|
+
const originalError = console.error;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.spyOn(console, 'error').mockImplementation((...args) => {
|
|
11
|
+
const pattern = /Warning: An update to .* inside a test was not wrapped in act/;
|
|
12
|
+
|
|
13
|
+
if (typeof args[0] === 'string' && pattern.test(args[0])) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return originalError.call(console, ...args);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
jest.clearAllMocks() // This syntax appeases typescript:
|
|
22
|
+
;
|
|
23
|
+
console.error.mockRestore();
|
|
24
|
+
});
|
|
25
|
+
it('renders in the default state initially', () => {
|
|
26
|
+
const {
|
|
27
|
+
result
|
|
28
|
+
} = renderHook(() => useCacheableSection('one'), {
|
|
29
|
+
wrapper: ({
|
|
30
|
+
children
|
|
31
|
+
}) => /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
32
|
+
offlineInterface: mockOfflineInterface
|
|
33
|
+
}, children)
|
|
34
|
+
});
|
|
35
|
+
expect(result.current.recordingState).toBe('default');
|
|
36
|
+
expect(result.current.isCached).toBe(false);
|
|
37
|
+
expect(result.current.lastUpdated).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
it('handles a successful recording', async done => {
|
|
40
|
+
const [sectionId, timeoutDelay] = ['one', 1234];
|
|
41
|
+
const testOfflineInterface = { ...mockOfflineInterface,
|
|
42
|
+
getCachedSections: jest.fn().mockResolvedValueOnce([]).mockResolvedValueOnce([{
|
|
43
|
+
sectionId: sectionId,
|
|
44
|
+
lastUpdated: new Date()
|
|
45
|
+
}])
|
|
46
|
+
};
|
|
47
|
+
const {
|
|
48
|
+
result,
|
|
49
|
+
waitFor
|
|
50
|
+
} = renderHook(() => useCacheableSection(sectionId), {
|
|
51
|
+
wrapper: ({
|
|
52
|
+
children
|
|
53
|
+
}) => /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
54
|
+
offlineInterface: testOfflineInterface
|
|
55
|
+
}, children)
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const assertRecordingStarted = () => {
|
|
59
|
+
expect(result.current.recordingState).toBe('recording');
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const assertRecordingCompleted = async () => {
|
|
63
|
+
expect(result.current.recordingState).toBe('default'); // Test that 'isCached' gets updated
|
|
64
|
+
|
|
65
|
+
expect(testOfflineInterface.getCachedSections).toBeCalledTimes(2);
|
|
66
|
+
await waitFor(() => result.current.isCached === true);
|
|
67
|
+
expect(result.current.isCached).toBe(true);
|
|
68
|
+
expect(result.current.lastUpdated).toBeInstanceOf(Date); // If this cb is not called, test should time out and fail
|
|
69
|
+
|
|
70
|
+
done();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
await act(async () => {
|
|
74
|
+
await result.current.startRecording({
|
|
75
|
+
onStarted: assertRecordingStarted,
|
|
76
|
+
onCompleted: assertRecordingCompleted,
|
|
77
|
+
recordingTimeoutDelay: timeoutDelay
|
|
78
|
+
});
|
|
79
|
+
}); // At this stage, recording should be 'pending'
|
|
80
|
+
|
|
81
|
+
expect(result.current.recordingState).toBe('pending'); // Check correct options sent to offline interface
|
|
82
|
+
|
|
83
|
+
const options = mockOfflineInterface.startRecording.mock.calls[0][0];
|
|
84
|
+
expect(options.sectionId).toBe(sectionId);
|
|
85
|
+
expect(options.recordingTimeoutDelay).toBe(timeoutDelay);
|
|
86
|
+
expect(typeof options.onStarted).toBe('function');
|
|
87
|
+
expect(typeof options.onCompleted).toBe('function');
|
|
88
|
+
expect(typeof options.onError).toBe('function'); // Make sure all async assertions are called
|
|
89
|
+
|
|
90
|
+
expect.assertions(11);
|
|
91
|
+
});
|
|
92
|
+
it('handles a recording that encounters an error', async done => {
|
|
93
|
+
// Suppress the expected error from console (in addition to 'act' warning)
|
|
94
|
+
jest.spyOn(console, 'error').mockImplementation((...args) => {
|
|
95
|
+
const actPattern = /Warning: An update to .* inside a test was not wrapped in act/;
|
|
96
|
+
const errPattern = /Error during recording/;
|
|
97
|
+
const matchesPattern = actPattern.test(args[0]) || errPattern.test(args[0]);
|
|
98
|
+
|
|
99
|
+
if (typeof args[0] === 'string' && matchesPattern) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return originalError.call(console, ...args);
|
|
104
|
+
});
|
|
105
|
+
const testOfflineInterface = { ...mockOfflineInterface,
|
|
106
|
+
startRecording: errorRecordingMock
|
|
107
|
+
};
|
|
108
|
+
const {
|
|
109
|
+
result
|
|
110
|
+
} = renderHook(() => useCacheableSection('one'), {
|
|
111
|
+
wrapper: ({
|
|
112
|
+
children
|
|
113
|
+
}) => /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
114
|
+
offlineInterface: testOfflineInterface
|
|
115
|
+
}, children)
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const assertRecordingStarted = () => {
|
|
119
|
+
expect(result.current.recordingState).toBe('recording');
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const assertRecordingError = error => {
|
|
123
|
+
expect(result.current.recordingState).toBe('error');
|
|
124
|
+
expect(error.message).toMatch(/test err/); // see errorRecordingMock
|
|
125
|
+
|
|
126
|
+
expect(console.error).toHaveBeenCalledWith('Error during recording:', error); // Expect only one call, from initialization:
|
|
127
|
+
|
|
128
|
+
expect(mockOfflineInterface.getCachedSections).toBeCalledTimes(1); // If this cb is not called, test should time out and fail
|
|
129
|
+
|
|
130
|
+
done();
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
await act(async () => {
|
|
134
|
+
await result.current.startRecording({
|
|
135
|
+
onStarted: assertRecordingStarted,
|
|
136
|
+
onError: assertRecordingError
|
|
137
|
+
});
|
|
138
|
+
}); // At this stage, recording should be 'pending'
|
|
139
|
+
|
|
140
|
+
expect(result.current.recordingState).toBe('pending'); // Make sure all async assertions are called
|
|
141
|
+
|
|
142
|
+
expect.assertions(6);
|
|
143
|
+
});
|
|
144
|
+
it('handles an error starting the recording', async () => {
|
|
145
|
+
const testOfflineInterface = { ...mockOfflineInterface,
|
|
146
|
+
startRecording: failedMessageRecordingMock
|
|
147
|
+
};
|
|
148
|
+
const {
|
|
149
|
+
result
|
|
150
|
+
} = renderHook(() => useCacheableSection('err'), {
|
|
151
|
+
wrapper: ({
|
|
152
|
+
children
|
|
153
|
+
}) => /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
154
|
+
offlineInterface: testOfflineInterface
|
|
155
|
+
}, children)
|
|
156
|
+
});
|
|
157
|
+
await expect(result.current.startRecording()).rejects.toThrow('Failed message' // from failedMessageRecordingMock
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
it('handles remove and updates sections', async () => {
|
|
161
|
+
const sectionId = 'one';
|
|
162
|
+
const testOfflineInterface = { ...mockOfflineInterface,
|
|
163
|
+
getCachedSections: jest.fn().mockResolvedValueOnce([{
|
|
164
|
+
sectionId: sectionId,
|
|
165
|
+
lastUpdated: new Date()
|
|
166
|
+
}]).mockResolvedValueOnce([])
|
|
167
|
+
};
|
|
168
|
+
const {
|
|
169
|
+
result,
|
|
170
|
+
waitFor
|
|
171
|
+
} = renderHook(() => useCacheableSection(sectionId), {
|
|
172
|
+
wrapper: ({
|
|
173
|
+
children
|
|
174
|
+
}) => /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
175
|
+
offlineInterface: testOfflineInterface
|
|
176
|
+
}, children)
|
|
177
|
+
}); // Wait for state to sync with indexedDB
|
|
178
|
+
|
|
179
|
+
await waitFor(() => result.current.isCached === true);
|
|
180
|
+
let success;
|
|
181
|
+
await act(async () => {
|
|
182
|
+
success = await result.current.remove();
|
|
183
|
+
});
|
|
184
|
+
expect(success).toBe(true); // Test that 'isCached' gets updated
|
|
185
|
+
|
|
186
|
+
expect(testOfflineInterface.getCachedSections).toBeCalledTimes(2);
|
|
187
|
+
await waitFor(() => result.current.isCached === false);
|
|
188
|
+
expect(result.current.isCached).toBe(false);
|
|
189
|
+
expect(result.current.lastUpdated).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
it('handles a change in ID', async () => {
|
|
192
|
+
const testOfflineInterface = { ...mockOfflineInterface,
|
|
193
|
+
getCachedSections: jest.fn().mockResolvedValue([{
|
|
194
|
+
sectionId: 'id-one',
|
|
195
|
+
lastUpdated: new Date()
|
|
196
|
+
}])
|
|
197
|
+
};
|
|
198
|
+
const {
|
|
199
|
+
result,
|
|
200
|
+
waitFor,
|
|
201
|
+
rerender
|
|
202
|
+
} = renderHook((...args) => useCacheableSection(...args), {
|
|
203
|
+
wrapper: ({
|
|
204
|
+
children
|
|
205
|
+
}) => /*#__PURE__*/React.createElement(OfflineProvider, {
|
|
206
|
+
offlineInterface: testOfflineInterface
|
|
207
|
+
}, children),
|
|
208
|
+
initialProps: 'id-one'
|
|
209
|
+
}); // Wait for state to sync with indexedDB
|
|
210
|
+
|
|
211
|
+
await waitFor(() => result.current.isCached === true);
|
|
212
|
+
rerender('id-two'); // Test that 'isCached' gets updated
|
|
213
|
+
// expect(testOfflineInterface.getCachedSections).toBeCalledTimes(2)
|
|
214
|
+
|
|
215
|
+
await waitFor(() => result.current.isCached === false);
|
|
216
|
+
expect(result.current.isCached).toBe(false);
|
|
217
|
+
expect(result.current.lastUpdated).toBeUndefined();
|
|
218
|
+
});
|