@shopgate/pwa-ui-material 7.31.4 → 7.31.5-beta.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/SnackBar/index.js +86 -22
- package/SnackBar/spec.js +293 -0
- package/package.json +2 -2
package/SnackBar/index.js
CHANGED
|
@@ -5,10 +5,17 @@ import { config } from 'react-spring';
|
|
|
5
5
|
import { Spring } from 'react-spring/renderprops.cjs';
|
|
6
6
|
import Ellipsis from '@shopgate/pwa-common/components/Ellipsis';
|
|
7
7
|
import { i18n } from '@shopgate/engage/core/helpers';
|
|
8
|
+
import { useLongPress } from '@shopgate/engage/core/hooks/events';
|
|
8
9
|
import { themeColors, themeShadows } from '@shopgate/pwa-common/helpers/config';
|
|
9
10
|
import { makeStyles } from '@shopgate/engage/styles';
|
|
10
11
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
12
|
const defaultToast = {};
|
|
13
|
+
|
|
14
|
+
// Approximate settle time of the slide-out (config.stiff). The current toast is removed from the
|
|
15
|
+
// queue this long after it starts sliding out, so the exit animation is fully visible before the
|
|
16
|
+
// next toast slides in. Timer-driven removal keeps queue advancement independent of the spring's
|
|
17
|
+
// onRest callback, which fires unreliably during the toast-to-toast handoff.
|
|
18
|
+
const EXIT_ANIMATION_MS = 500;
|
|
12
19
|
const backgroundColor = themeColors.lightDark;
|
|
13
20
|
const buttonColor = themeColors.accent;
|
|
14
21
|
const buttonColorContrast = Color(buttonColor).contrast(Color(backgroundColor));
|
|
@@ -94,48 +101,106 @@ const SnackBar = ({
|
|
|
94
101
|
cx
|
|
95
102
|
} = useStyles();
|
|
96
103
|
const toasts = useMemo(() => toastsProp || [], [toastsProp]);
|
|
104
|
+
const current = toasts.length ? toasts[0] : null;
|
|
105
|
+
const currentId = current ? current.id : null;
|
|
97
106
|
const [visible, setVisible] = useState(true);
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
const autoHideTimer = useRef(null);
|
|
108
|
+
const exitTimer = useRef(null);
|
|
109
|
+
const pressingRef = useRef(false);
|
|
110
|
+
|
|
111
|
+
// Latest values reachable from the stable timer callbacks below without re-creating them.
|
|
112
|
+
const durationRef = useRef();
|
|
113
|
+
durationRef.current = current && current.duration || 2500;
|
|
114
|
+
const removeToastRef = useRef(removeToast);
|
|
115
|
+
removeToastRef.current = removeToast;
|
|
116
|
+
|
|
117
|
+
// Show whenever there is a toast to display; hide when the queue drains. Reacting to the queue
|
|
118
|
+
// length is what slides the next toast in after the current one has been removed.
|
|
103
119
|
useEffect(() => {
|
|
104
120
|
setVisible(toasts.length > 0);
|
|
105
121
|
}, [toasts.length]);
|
|
122
|
+
useEffect(() => () => {
|
|
123
|
+
clearTimeout(autoHideTimer.current);
|
|
124
|
+
clearTimeout(exitTimer.current);
|
|
125
|
+
}, []);
|
|
106
126
|
const snack = useMemo(() => {
|
|
107
|
-
const raw =
|
|
127
|
+
const raw = current || defaultToast;
|
|
108
128
|
return {
|
|
109
129
|
...raw,
|
|
110
130
|
message: i18n.text(raw.message || '', raw.messageParams || {}),
|
|
111
131
|
actionLabel: i18n.text(raw.actionLabel || '')
|
|
112
132
|
};
|
|
113
|
-
}, [
|
|
133
|
+
}, [current]);
|
|
134
|
+
|
|
135
|
+
// Slide the current toast out, then advance the queue once the exit animation has played. The
|
|
136
|
+
// removal is timer-driven (not the spring's onRest), so it fires exactly once for the toast being
|
|
137
|
+
// dismissed and can't be re-triggered by the spring re-settling as the next toast slides in.
|
|
114
138
|
const hide = useCallback(() => {
|
|
115
|
-
clearTimeout(
|
|
139
|
+
clearTimeout(autoHideTimer.current);
|
|
140
|
+
clearTimeout(exitTimer.current);
|
|
116
141
|
setVisible(false);
|
|
142
|
+
exitTimer.current = setTimeout(() => removeToastRef.current(), EXIT_ANIMATION_MS);
|
|
117
143
|
}, []);
|
|
144
|
+
|
|
145
|
+
// (Re)starts the auto-hide countdown. Stable identity (reads the duration via a ref), so the
|
|
146
|
+
// effect below only re-runs when the shown toast actually changes.
|
|
147
|
+
const scheduleAutoHide = useCallback(() => {
|
|
148
|
+
clearTimeout(autoHideTimer.current);
|
|
149
|
+
autoHideTimer.current = setTimeout(hide, durationRef.current);
|
|
150
|
+
}, [hide]);
|
|
151
|
+
|
|
152
|
+
// Start the countdown once per shown toast, keyed on its id — NOT on the spring's onRest. With
|
|
153
|
+
// `force`, onRest fires on every re-render, so scheduling the auto-hide there restarted the
|
|
154
|
+
// countdown on each render and could keep a toast on screen far longer than its duration.
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!currentId) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
scheduleAutoHide();
|
|
160
|
+
return () => clearTimeout(autoHideTimer.current);
|
|
161
|
+
}, [currentId, scheduleAutoHide]);
|
|
118
162
|
const handleAction = useCallback(() => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
163
|
+
current?.action?.();
|
|
164
|
+
hide();
|
|
165
|
+
}, [current, hide]);
|
|
166
|
+
const handleLongPress = useCallback(() => {
|
|
167
|
+
pressingRef.current = false;
|
|
168
|
+
if (snack.onLongPress) {
|
|
169
|
+
snack.onLongPress();
|
|
122
170
|
}
|
|
123
171
|
hide();
|
|
124
|
-
}, [
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
172
|
+
}, [snack, hide]);
|
|
173
|
+
|
|
174
|
+
// While a long press is held, freeze the auto-hide countdown so the toast can't disappear
|
|
175
|
+
// mid-press. A press released before the threshold resumes the countdown from the start.
|
|
176
|
+
const handlePressStart = useCallback(() => {
|
|
177
|
+
pressingRef.current = true;
|
|
178
|
+
clearTimeout(autoHideTimer.current);
|
|
179
|
+
}, []);
|
|
180
|
+
const handlePressCancel = useCallback(() => {
|
|
181
|
+
pressingRef.current = false;
|
|
182
|
+
if (currentId) {
|
|
183
|
+
scheduleAutoHide();
|
|
131
184
|
}
|
|
132
|
-
}, [
|
|
185
|
+
}, [currentId, scheduleAutoHide]);
|
|
186
|
+
const longPressHandlers = useLongPress(handleLongPress, {
|
|
187
|
+
threshold: 4000,
|
|
188
|
+
onStart: handlePressStart,
|
|
189
|
+
onCancel: handlePressCancel
|
|
190
|
+
});
|
|
133
191
|
const {
|
|
134
192
|
action = null,
|
|
135
193
|
actionLabel = null,
|
|
136
|
-
message = null
|
|
194
|
+
message = null,
|
|
195
|
+
onLongPress = null
|
|
137
196
|
} = snack;
|
|
138
|
-
|
|
197
|
+
|
|
198
|
+
// A toast is either long-pressable or has a whole-box click action — never both. Attaching both
|
|
199
|
+
// would let a single long press fire onLongPress (at the threshold) and then handleAction (on the
|
|
200
|
+
// release click). Long-press takes precedence.
|
|
201
|
+
const boxProps = onLongPress ? {
|
|
202
|
+
...longPressHandlers
|
|
203
|
+
} : {
|
|
139
204
|
...(action && !actionLabel && {
|
|
140
205
|
onClick: handleAction
|
|
141
206
|
})
|
|
@@ -157,7 +222,6 @@ const SnackBar = ({
|
|
|
157
222
|
config: config.stiff,
|
|
158
223
|
reverse: !visible,
|
|
159
224
|
force: true,
|
|
160
|
-
onRest: handleRest,
|
|
161
225
|
children: springProps =>
|
|
162
226
|
/*#__PURE__*/
|
|
163
227
|
// eslint-disable-next-line max-len
|
package/SnackBar/spec.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { act } from 'react-dom/test-utils';
|
|
4
|
+
import SnackBar from "./index";
|
|
5
|
+
|
|
6
|
+
// Use the real long-press hook (the barrel would otherwise drag in appEvents/pwa-core).
|
|
7
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
+
jest.mock('@shopgate/engage/core/hooks/events', () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
useLongPress: jest.requireActual('@shopgate/engage/core/hooks/events/useLongPress').default
|
|
11
|
+
}));
|
|
12
|
+
jest.mock('@shopgate/engage/core/helpers', () => ({
|
|
13
|
+
i18n: {
|
|
14
|
+
text: input => input || ''
|
|
15
|
+
}
|
|
16
|
+
}));
|
|
17
|
+
jest.mock('@shopgate/pwa-common/helpers/config', () => ({
|
|
18
|
+
themeColors: {
|
|
19
|
+
light: '#ffffff',
|
|
20
|
+
lightDark: '#333333',
|
|
21
|
+
accent: '#00aaff'
|
|
22
|
+
},
|
|
23
|
+
themeShadows: {
|
|
24
|
+
toast: 'none'
|
|
25
|
+
}
|
|
26
|
+
}));
|
|
27
|
+
jest.mock('@shopgate/pwa-common/components/Ellipsis', () => ({
|
|
28
|
+
__esModule: true,
|
|
29
|
+
default: ({
|
|
30
|
+
children
|
|
31
|
+
}) => children
|
|
32
|
+
}));
|
|
33
|
+
jest.mock('@shopgate/engage/styles', () => ({
|
|
34
|
+
makeStyles: () => () => () => ({
|
|
35
|
+
classes: new Proxy({}, {
|
|
36
|
+
get: (_, prop) => prop
|
|
37
|
+
}),
|
|
38
|
+
cx: (...args) => args.filter(Boolean).join(' ')
|
|
39
|
+
})
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Render the Spring render-prop child immediately without animating. onRest is fired on a 0ms
|
|
43
|
+
// timer to mirror react-spring calling it after the commit — this drives the auto-hide countdown.
|
|
44
|
+
jest.mock('react-spring', () => ({
|
|
45
|
+
config: {
|
|
46
|
+
stiff: {}
|
|
47
|
+
}
|
|
48
|
+
}));
|
|
49
|
+
jest.mock('react-spring/renderprops.cjs', () => {
|
|
50
|
+
// eslint-disable-next-line global-require
|
|
51
|
+
const {
|
|
52
|
+
useEffect: useSpringEffect
|
|
53
|
+
} = require('react');
|
|
54
|
+
return {
|
|
55
|
+
Spring: ({
|
|
56
|
+
children,
|
|
57
|
+
onRest
|
|
58
|
+
}) => {
|
|
59
|
+
useSpringEffect(() => {
|
|
60
|
+
const id = setTimeout(() => onRest && onRest(), 0);
|
|
61
|
+
return () => clearTimeout(id);
|
|
62
|
+
});
|
|
63
|
+
return children({});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
describe('<SnackBar />', () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
jest.useFakeTimers();
|
|
70
|
+
});
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
jest.clearAllTimers();
|
|
73
|
+
jest.useRealTimers();
|
|
74
|
+
});
|
|
75
|
+
it('should invoke onLongPress after a long press on a toast that provides one', () => {
|
|
76
|
+
const onLongPress = jest.fn();
|
|
77
|
+
const toasts = [{
|
|
78
|
+
id: 'pipeline.error',
|
|
79
|
+
message: 'error.general',
|
|
80
|
+
onLongPress,
|
|
81
|
+
duration: 5000
|
|
82
|
+
}];
|
|
83
|
+
render(/*#__PURE__*/_jsx(SnackBar, {
|
|
84
|
+
removeToast: jest.fn(),
|
|
85
|
+
toasts: toasts
|
|
86
|
+
}));
|
|
87
|
+
const message = screen.getByText('error.general');
|
|
88
|
+
fireEvent.mouseDown(message);
|
|
89
|
+
act(() => {
|
|
90
|
+
jest.advanceTimersByTime(4000);
|
|
91
|
+
});
|
|
92
|
+
fireEvent.mouseUp(message);
|
|
93
|
+
expect(onLongPress).toHaveBeenCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
it('should keep the toast open while it is being long-pressed instead of auto-hiding', () => {
|
|
96
|
+
const onLongPress = jest.fn();
|
|
97
|
+
const removeToast = jest.fn();
|
|
98
|
+
const toasts = [{
|
|
99
|
+
id: 'pipeline.error',
|
|
100
|
+
message: 'error.general',
|
|
101
|
+
onLongPress,
|
|
102
|
+
duration: 2500
|
|
103
|
+
}];
|
|
104
|
+
render(/*#__PURE__*/_jsx(SnackBar, {
|
|
105
|
+
removeToast: removeToast,
|
|
106
|
+
toasts: toasts
|
|
107
|
+
}));
|
|
108
|
+
const message = screen.getByText('error.general');
|
|
109
|
+
fireEvent.mouseDown(message);
|
|
110
|
+
|
|
111
|
+
// Hold well past the toast's 2500ms lifetime: the auto-hide countdown must be frozen, so the
|
|
112
|
+
// toast neither hides nor gets removed while the press is ongoing.
|
|
113
|
+
act(() => {
|
|
114
|
+
jest.advanceTimersByTime(3000);
|
|
115
|
+
});
|
|
116
|
+
expect(onLongPress).not.toHaveBeenCalled();
|
|
117
|
+
expect(removeToast).not.toHaveBeenCalled();
|
|
118
|
+
|
|
119
|
+
// The long press still completes at its 4000ms threshold.
|
|
120
|
+
act(() => {
|
|
121
|
+
jest.advanceTimersByTime(1000);
|
|
122
|
+
});
|
|
123
|
+
expect(onLongPress).toHaveBeenCalledTimes(1);
|
|
124
|
+
});
|
|
125
|
+
it('should invoke the pressed toast\'s handler even if the toast is mutated in place mid-press', () => {
|
|
126
|
+
// ToastProvider updates a same-id toast by mutating it in place, so the long-press must fire
|
|
127
|
+
// the handler captured when the press started, not whatever toasts[0].onLongPress became.
|
|
128
|
+
const pressedHandler = jest.fn();
|
|
129
|
+
const replacementHandler = jest.fn();
|
|
130
|
+
const toasts = [{
|
|
131
|
+
id: 'pipeline.error',
|
|
132
|
+
message: 'error.general',
|
|
133
|
+
onLongPress: pressedHandler,
|
|
134
|
+
duration: 5000
|
|
135
|
+
}];
|
|
136
|
+
render(/*#__PURE__*/_jsx(SnackBar, {
|
|
137
|
+
removeToast: jest.fn(),
|
|
138
|
+
toasts: toasts
|
|
139
|
+
}));
|
|
140
|
+
const message = screen.getByText('error.general');
|
|
141
|
+
fireEvent.mouseDown(message);
|
|
142
|
+
// A second pipeline error arrives during the press and overwrites the toast's handler in place.
|
|
143
|
+
toasts[0].onLongPress = replacementHandler;
|
|
144
|
+
act(() => {
|
|
145
|
+
jest.advanceTimersByTime(4000);
|
|
146
|
+
});
|
|
147
|
+
fireEvent.mouseUp(message);
|
|
148
|
+
expect(pressedHandler).toHaveBeenCalledTimes(1);
|
|
149
|
+
expect(replacementHandler).not.toHaveBeenCalled();
|
|
150
|
+
});
|
|
151
|
+
it('should not also fire the box click action on a long press when onLongPress is present', () => {
|
|
152
|
+
const onLongPress = jest.fn();
|
|
153
|
+
const action = jest.fn();
|
|
154
|
+
const toasts = [{
|
|
155
|
+
id: 'pipeline.error',
|
|
156
|
+
message: 'error.general',
|
|
157
|
+
onLongPress,
|
|
158
|
+
action,
|
|
159
|
+
// no actionLabel → would otherwise become a whole-box onClick
|
|
160
|
+
duration: 5000
|
|
161
|
+
}];
|
|
162
|
+
render(/*#__PURE__*/_jsx(SnackBar, {
|
|
163
|
+
removeToast: jest.fn(),
|
|
164
|
+
toasts: toasts
|
|
165
|
+
}));
|
|
166
|
+
const message = screen.getByText('error.general');
|
|
167
|
+
fireEvent.mouseDown(message);
|
|
168
|
+
act(() => {
|
|
169
|
+
jest.advanceTimersByTime(4000);
|
|
170
|
+
});
|
|
171
|
+
fireEvent.mouseUp(message);
|
|
172
|
+
// The pointer release also produces a click, which must not run the action too.
|
|
173
|
+
fireEvent.click(message);
|
|
174
|
+
expect(onLongPress).toHaveBeenCalledTimes(1);
|
|
175
|
+
expect(action).not.toHaveBeenCalled();
|
|
176
|
+
});
|
|
177
|
+
it('should not invoke onLongPress when the press is released before the threshold', () => {
|
|
178
|
+
const onLongPress = jest.fn();
|
|
179
|
+
const toasts = [{
|
|
180
|
+
id: 'pipeline.error',
|
|
181
|
+
message: 'error.general',
|
|
182
|
+
onLongPress,
|
|
183
|
+
duration: 5000
|
|
184
|
+
}];
|
|
185
|
+
render(/*#__PURE__*/_jsx(SnackBar, {
|
|
186
|
+
removeToast: jest.fn(),
|
|
187
|
+
toasts: toasts
|
|
188
|
+
}));
|
|
189
|
+
const message = screen.getByText('error.general');
|
|
190
|
+
fireEvent.mouseDown(message);
|
|
191
|
+
act(() => {
|
|
192
|
+
jest.advanceTimersByTime(300);
|
|
193
|
+
});
|
|
194
|
+
fireEvent.mouseUp(message);
|
|
195
|
+
act(() => {
|
|
196
|
+
jest.advanceTimersByTime(500);
|
|
197
|
+
});
|
|
198
|
+
expect(onLongPress).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
it('should not attach long-press handlers when the toast has no onLongPress', () => {
|
|
201
|
+
const toasts = [{
|
|
202
|
+
id: 'pipeline.error',
|
|
203
|
+
message: 'error.general',
|
|
204
|
+
duration: 5000
|
|
205
|
+
}];
|
|
206
|
+
render(/*#__PURE__*/_jsx(SnackBar, {
|
|
207
|
+
removeToast: jest.fn(),
|
|
208
|
+
toasts: toasts
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
// A long press must be a no-op (and must not throw) when no handler is provided.
|
|
212
|
+
const message = screen.getByText('error.general');
|
|
213
|
+
fireEvent.mouseDown(message);
|
|
214
|
+
expect(() => act(() => {
|
|
215
|
+
jest.advanceTimersByTime(500);
|
|
216
|
+
})).not.toThrow();
|
|
217
|
+
});
|
|
218
|
+
it('should play each queued toast in turn — the second is not skipped or instantly removed', () => {
|
|
219
|
+
// Mirrors ToastProvider: a FIFO queue whose head is dropped (immutably) on removeToast.
|
|
220
|
+
const Harness = () => {
|
|
221
|
+
const [toasts, setToasts] = useState([{
|
|
222
|
+
id: 'a',
|
|
223
|
+
message: 'first.toast',
|
|
224
|
+
duration: 2500
|
|
225
|
+
}, {
|
|
226
|
+
id: 'b',
|
|
227
|
+
message: 'second.toast',
|
|
228
|
+
duration: 2500
|
|
229
|
+
}]);
|
|
230
|
+
const removeToast = useCallback(() => setToasts(t => t.slice(1)), []);
|
|
231
|
+
if (!toasts.length) return null;
|
|
232
|
+
return /*#__PURE__*/_jsx(SnackBar, {
|
|
233
|
+
removeToast: removeToast,
|
|
234
|
+
toasts: toasts
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
render(/*#__PURE__*/_jsx(Harness, {}));
|
|
238
|
+
|
|
239
|
+
// First toast is shown; the second is still queued, not yet rendered.
|
|
240
|
+
expect(screen.getByText('first.toast')).toBeTruthy();
|
|
241
|
+
expect(screen.queryByText('second.toast')).toBe(null);
|
|
242
|
+
|
|
243
|
+
// First toast dwells (2500ms), then slides out and is removed after the exit animation.
|
|
244
|
+
act(() => {
|
|
245
|
+
jest.advanceTimersByTime(2500 + 600);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// The second toast is now on screen — it got its turn rather than being dropped instantly.
|
|
249
|
+
expect(screen.getByText('second.toast')).toBeTruthy();
|
|
250
|
+
expect(screen.queryByText('first.toast')).toBe(null);
|
|
251
|
+
|
|
252
|
+
// It in turn dwells and is removed, draining the queue.
|
|
253
|
+
act(() => {
|
|
254
|
+
jest.advanceTimersByTime(2500 + 600);
|
|
255
|
+
});
|
|
256
|
+
expect(screen.queryByText('second.toast')).toBe(null);
|
|
257
|
+
});
|
|
258
|
+
it('auto-hides within its duration even when the parent re-renders repeatedly', () => {
|
|
259
|
+
// The spring re-settles (and fires onRest) on every render because of `force`. The auto-hide
|
|
260
|
+
// countdown must therefore NOT be tied to onRest, or frequent parent re-renders would keep
|
|
261
|
+
// resetting it and the toast would outlive its duration.
|
|
262
|
+
const removeToast = jest.fn();
|
|
263
|
+
const Harness = () => {
|
|
264
|
+
const [, tick] = useState(0);
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
const id = setInterval(() => tick(n => n + 1), 500); // re-render every 500ms (< duration)
|
|
267
|
+
return () => clearInterval(id);
|
|
268
|
+
}, []);
|
|
269
|
+
const toasts = [{
|
|
270
|
+
id: 'a',
|
|
271
|
+
message: 'error.general',
|
|
272
|
+
duration: 2500
|
|
273
|
+
}];
|
|
274
|
+
return /*#__PURE__*/_jsx(SnackBar, {
|
|
275
|
+
removeToast: removeToast,
|
|
276
|
+
toasts: toasts
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
render(/*#__PURE__*/_jsx(Harness, {}));
|
|
280
|
+
|
|
281
|
+
// Not yet gone before its duration elapses.
|
|
282
|
+
act(() => {
|
|
283
|
+
jest.advanceTimersByTime(2000);
|
|
284
|
+
});
|
|
285
|
+
expect(removeToast).not.toHaveBeenCalled();
|
|
286
|
+
|
|
287
|
+
// Removed shortly after its duration (dwell + exit animation), despite the periodic re-renders.
|
|
288
|
+
act(() => {
|
|
289
|
+
jest.advanceTimersByTime(500 + 600);
|
|
290
|
+
});
|
|
291
|
+
expect(removeToast).toHaveBeenCalledTimes(1);
|
|
292
|
+
});
|
|
293
|
+
});
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopgate/pwa-ui-material",
|
|
3
|
-
"version": "7.31.
|
|
3
|
+
"version": "7.31.5-beta.1",
|
|
4
4
|
"description": "Shopgate's material design UI components.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"@shopgate/pwa-common": "7.31.
|
|
8
|
+
"@shopgate/pwa-common": "7.31.5-beta.1",
|
|
9
9
|
"@types/color": "^4.2.0",
|
|
10
10
|
"@types/react-transition-group": "^4.4.12",
|
|
11
11
|
"react": "^17.0.2",
|