@dhis2/app-service-offline
Advanced tools
Comparing version 2.9.1 to 2.9.2
"use strict"; | ||
var _react = require("@testing-library/react"); | ||
var _reactHooks = require("@testing-library/react-hooks"); | ||
var _react2 = _interopRequireDefault(require("react")); | ||
var _onlineStatus = require("../online-status"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function wait(ms) { | ||
return new Promise(resolve => { | ||
setTimeout(() => resolve(), ms); | ||
}); | ||
} | ||
beforeEach(() => { | ||
@@ -172,2 +184,128 @@ jest.restoreAllMocks(); | ||
}); | ||
it('handles debounced state change when parent component rerenders during a debounce delay', async () => { | ||
jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(true); | ||
const events = {}; | ||
window.addEventListener = jest.fn((event, cb) => events[event] = cb); | ||
const TestComponent = () => { | ||
const { | ||
online | ||
} = (0, _onlineStatus.useOnlineStatus)({ | ||
debounceDelay: 50 | ||
}); | ||
return /*#__PURE__*/_react2.default.createElement("div", { | ||
"data-testid": "status" | ||
}, online ? 'on' : 'off'); | ||
}; | ||
const { | ||
rerender | ||
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(TestComponent, null)); | ||
const { | ||
getByTestId | ||
} = _react.screen; | ||
expect(getByTestId('status')).toHaveTextContent('on'); | ||
await (0, _reactHooks.act)(async () => { | ||
// Multiple events in succession | ||
events.offline(new Event('offline')); | ||
events.online(new Event('online')); | ||
events.offline(new Event('offline')); | ||
}); // Immediately, nothing should happen | ||
expect(getByTestId('status')).toHaveTextContent('on'); // Rerender parent component | ||
rerender( /*#__PURE__*/_react2.default.createElement(TestComponent, null)); // Final "offline" event should still resolve | ||
await (0, _react.waitFor)(() => expect(getByTestId('status')).toHaveTextContent('off')); | ||
}); | ||
it('handles debounced state change when debounce delay is changed during a delay', async () => { | ||
jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(true); | ||
const events = {}; | ||
window.addEventListener = jest.fn((event, cb) => events[event] = cb); | ||
const TestComponent = ({ | ||
options | ||
}) => { | ||
const { | ||
online | ||
} = (0, _onlineStatus.useOnlineStatus)(options); | ||
return /*#__PURE__*/_react2.default.createElement("div", { | ||
"data-testid": "status" | ||
}, online ? 'on' : 'off'); | ||
}; | ||
const { | ||
rerender | ||
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(TestComponent, { | ||
options: { | ||
debounceDelay: 100 | ||
} | ||
})); | ||
const { | ||
getByTestId | ||
} = _react.screen; | ||
expect(getByTestId('status')).toHaveTextContent('on'); | ||
await (0, _reactHooks.act)(async () => { | ||
// Multiple events in succession | ||
events.offline(new Event('offline')); | ||
events.online(new Event('online')); | ||
events.offline(new Event('offline')); | ||
}); // Immediately, nothing should happen | ||
expect(getByTestId('status')).toHaveTextContent('on'); // Change debounce options | ||
rerender( /*#__PURE__*/_react2.default.createElement(TestComponent, { | ||
options: { | ||
debounceDelay: 50 | ||
} | ||
})); // Final "offline" event should still resolve | ||
await (0, _react.waitFor)(() => expect(getByTestId('status')).toHaveTextContent('off')); | ||
}); | ||
it('debounces consistently across rerenders', async () => { | ||
jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(true); | ||
const events = {}; | ||
window.addEventListener = jest.fn((event, cb) => events[event] = cb); | ||
const TestComponent = () => { | ||
const { | ||
online | ||
} = (0, _onlineStatus.useOnlineStatus)({ | ||
debounceDelay: 100 | ||
}); | ||
return /*#__PURE__*/_react2.default.createElement("div", { | ||
"data-testid": "status" | ||
}, online ? 'on' : 'off'); | ||
}; | ||
const { | ||
rerender | ||
} = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(TestComponent, null)); | ||
const { | ||
getByTestId | ||
} = _react.screen; | ||
expect(getByTestId('status')).toHaveTextContent('on'); | ||
await (0, _reactHooks.act)(async () => { | ||
// Multiple events in succession | ||
events.offline(new Event('offline')); | ||
events.online(new Event('online')); | ||
events.offline(new Event('offline')); | ||
}); // wait a little bit - not long enough for debounce to resolve | ||
await wait(50); | ||
expect(getByTestId('status')).toHaveTextContent('on'); // Rerender parent component | ||
rerender( /*#__PURE__*/_react2.default.createElement(TestComponent, null)); // Trigger more events | ||
await (0, _reactHooks.act)(async () => { | ||
events.online(new Event('online')); | ||
events.offline(new Event('offline')); | ||
}); // wait a little more - long enough that the first debounced callbacks | ||
// _would_ have resolved if there weren't the second set of events | ||
await wait(60); | ||
expect(getByTestId('status')).toHaveTextContent('on'); // wait long enough for second set of callbacks to resolve | ||
await (0, _react.waitFor)(() => expect(getByTestId('status')).toHaveTextContent('off')); | ||
}); | ||
}); |
@@ -27,3 +27,3 @@ "use strict"; | ||
*/ | ||
function useOnlineStatus(options) { | ||
function useOnlineStatus(options = {}) { | ||
// initialize state to `navigator.onLine` value | ||
@@ -34,3 +34,3 @@ const [online, setOnline] = (0, _react.useState)(navigator.onLine); // eslint-disable-next-line react-hooks/exhaustive-deps | ||
type | ||
}) => setOnline(type === 'online'), (options === null || options === void 0 ? void 0 : options.debounceDelay) || 1000), [options]); // on 'online' or 'offline' events, set state | ||
}) => setOnline(type === 'online'), options.debounceDelay || 1000), [options.debounceDelay]); // on 'online' or 'offline' events, set state | ||
@@ -41,3 +41,3 @@ (0, _react.useEffect)(() => { | ||
return () => { | ||
updateState.cancel(); | ||
updateState.flush(); | ||
window.removeEventListener('online', updateState); | ||
@@ -44,0 +44,0 @@ window.removeEventListener('offline', updateState); |
@@ -0,3 +1,12 @@ | ||
import { render, screen, waitFor } from '@testing-library/react'; | ||
import { act, renderHook } from '@testing-library/react-hooks'; | ||
import React from 'react'; | ||
import { useOnlineStatus } from '../online-status'; | ||
function wait(ms) { | ||
return new Promise(resolve => { | ||
setTimeout(() => resolve(), ms); | ||
}); | ||
} | ||
beforeEach(() => { | ||
@@ -168,2 +177,128 @@ jest.restoreAllMocks(); | ||
}); | ||
it('handles debounced state change when parent component rerenders during a debounce delay', async () => { | ||
jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(true); | ||
const events = {}; | ||
window.addEventListener = jest.fn((event, cb) => events[event] = cb); | ||
const TestComponent = () => { | ||
const { | ||
online | ||
} = useOnlineStatus({ | ||
debounceDelay: 50 | ||
}); | ||
return /*#__PURE__*/React.createElement("div", { | ||
"data-testid": "status" | ||
}, online ? 'on' : 'off'); | ||
}; | ||
const { | ||
rerender | ||
} = render( /*#__PURE__*/React.createElement(TestComponent, null)); | ||
const { | ||
getByTestId | ||
} = screen; | ||
expect(getByTestId('status')).toHaveTextContent('on'); | ||
await act(async () => { | ||
// Multiple events in succession | ||
events.offline(new Event('offline')); | ||
events.online(new Event('online')); | ||
events.offline(new Event('offline')); | ||
}); // Immediately, nothing should happen | ||
expect(getByTestId('status')).toHaveTextContent('on'); // Rerender parent component | ||
rerender( /*#__PURE__*/React.createElement(TestComponent, null)); // Final "offline" event should still resolve | ||
await waitFor(() => expect(getByTestId('status')).toHaveTextContent('off')); | ||
}); | ||
it('handles debounced state change when debounce delay is changed during a delay', async () => { | ||
jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(true); | ||
const events = {}; | ||
window.addEventListener = jest.fn((event, cb) => events[event] = cb); | ||
const TestComponent = ({ | ||
options | ||
}) => { | ||
const { | ||
online | ||
} = useOnlineStatus(options); | ||
return /*#__PURE__*/React.createElement("div", { | ||
"data-testid": "status" | ||
}, online ? 'on' : 'off'); | ||
}; | ||
const { | ||
rerender | ||
} = render( /*#__PURE__*/React.createElement(TestComponent, { | ||
options: { | ||
debounceDelay: 100 | ||
} | ||
})); | ||
const { | ||
getByTestId | ||
} = screen; | ||
expect(getByTestId('status')).toHaveTextContent('on'); | ||
await act(async () => { | ||
// Multiple events in succession | ||
events.offline(new Event('offline')); | ||
events.online(new Event('online')); | ||
events.offline(new Event('offline')); | ||
}); // Immediately, nothing should happen | ||
expect(getByTestId('status')).toHaveTextContent('on'); // Change debounce options | ||
rerender( /*#__PURE__*/React.createElement(TestComponent, { | ||
options: { | ||
debounceDelay: 50 | ||
} | ||
})); // Final "offline" event should still resolve | ||
await waitFor(() => expect(getByTestId('status')).toHaveTextContent('off')); | ||
}); | ||
it('debounces consistently across rerenders', async () => { | ||
jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(true); | ||
const events = {}; | ||
window.addEventListener = jest.fn((event, cb) => events[event] = cb); | ||
const TestComponent = () => { | ||
const { | ||
online | ||
} = useOnlineStatus({ | ||
debounceDelay: 100 | ||
}); | ||
return /*#__PURE__*/React.createElement("div", { | ||
"data-testid": "status" | ||
}, online ? 'on' : 'off'); | ||
}; | ||
const { | ||
rerender | ||
} = render( /*#__PURE__*/React.createElement(TestComponent, null)); | ||
const { | ||
getByTestId | ||
} = screen; | ||
expect(getByTestId('status')).toHaveTextContent('on'); | ||
await act(async () => { | ||
// Multiple events in succession | ||
events.offline(new Event('offline')); | ||
events.online(new Event('online')); | ||
events.offline(new Event('offline')); | ||
}); // wait a little bit - not long enough for debounce to resolve | ||
await wait(50); | ||
expect(getByTestId('status')).toHaveTextContent('on'); // Rerender parent component | ||
rerender( /*#__PURE__*/React.createElement(TestComponent, null)); // Trigger more events | ||
await act(async () => { | ||
events.online(new Event('online')); | ||
events.offline(new Event('offline')); | ||
}); // wait a little more - long enough that the first debounced callbacks | ||
// _would_ have resolved if there weren't the second set of events | ||
await wait(60); | ||
expect(getByTestId('status')).toHaveTextContent('on'); // wait long enough for second set of callbacks to resolve | ||
await waitFor(() => expect(getByTestId('status')).toHaveTextContent('off')); | ||
}); | ||
}); |
@@ -16,3 +16,3 @@ import debounce from 'lodash/debounce'; | ||
*/ | ||
export function useOnlineStatus(options) { | ||
export function useOnlineStatus(options = {}) { | ||
// initialize state to `navigator.onLine` value | ||
@@ -23,3 +23,3 @@ const [online, setOnline] = useState(navigator.onLine); // eslint-disable-next-line react-hooks/exhaustive-deps | ||
type | ||
}) => setOnline(type === 'online'), (options === null || options === void 0 ? void 0 : options.debounceDelay) || 1000), [options]); // on 'online' or 'offline' events, set state | ||
}) => setOnline(type === 'online'), options.debounceDelay || 1000), [options.debounceDelay]); // on 'online' or 'offline' events, set state | ||
@@ -30,3 +30,3 @@ useEffect(() => { | ||
return () => { | ||
updateState.cancel(); | ||
updateState.flush(); | ||
window.removeEventListener('online', updateState); | ||
@@ -33,0 +33,0 @@ window.removeEventListener('offline', updateState); |
{ | ||
"name": "@dhis2/app-service-offline", | ||
"description": "A runtime service for online/offline detection and offline caching", | ||
"version": "2.9.1", | ||
"version": "2.9.2", | ||
"main": "./build/cjs/index.js", | ||
@@ -36,7 +36,10 @@ "module": "./build/es/index.js", | ||
"peerDependencies": { | ||
"@dhis2/app-service-alerts": "2.9.1", | ||
"@dhis2/app-service-alerts": "2.9.2", | ||
"prop-types": "^15.7.2", | ||
"react": "^16.8.6", | ||
"react-dom": "^16.8.6" | ||
}, | ||
"dependencies": { | ||
"lodash": "^4.17.21" | ||
} | ||
} |
27159
649
5
+ Addedlodash@^4.17.21
+ Added@dhis2/app-service-alerts@2.9.2(transitive)
+ Addedlodash@4.17.21(transitive)
- Removed@dhis2/app-service-alerts@2.9.1(transitive)