@wise/dynamic-flow-client-internal 5.32.0 → 5.33.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.
Files changed (65) hide show
  1. package/build/dynamicFlow/DynamicFlow.js +11 -0
  2. package/build/dynamicFlow/DynamicFlowModal.js +21 -0
  3. package/build/dynamicFlow/DynamicFlowWiseProps.js +1 -0
  4. package/build/dynamicFlow/DynamicFlowWithRef.js +19 -0
  5. package/build/dynamicFlow/context-menu/useContextMenu.js +33 -0
  6. package/build/dynamicFlow/context-menu/useDFContextMenu.js +135 -0
  7. package/build/dynamicFlow/context-menu/useDFContextMenu.test.js +101 -0
  8. package/build/dynamicFlow/getMergedRenderers.js +36 -0
  9. package/build/dynamicFlow/messages.js +13 -0
  10. package/build/dynamicFlow/renderers.js +10 -0
  11. package/build/dynamicFlow/telemetry/app-version.js +4 -0
  12. package/build/dynamicFlow/telemetry/dispatchAnalyticsEvent.js +8 -0
  13. package/build/dynamicFlow/telemetry/getLogEvent.js +22 -0
  14. package/build/dynamicFlow/telemetry/getTrackEvent.js +39 -0
  15. package/build/dynamicFlow/telemetry/index.js +2 -0
  16. package/build/dynamicFlow/useCreateSnackBar.js +9 -0
  17. package/build/dynamicFlow/useOnCopy.js +16 -0
  18. package/build/dynamicFlow/useWiseHttpClient.js +24 -0
  19. package/build/dynamicFlow/useWiseToCoreProps.js +42 -0
  20. package/build/i18n/cs.json +2 -2
  21. package/build/i18n/hu.json +2 -2
  22. package/build/i18n/index.js +43 -0
  23. package/build/i18n/it.json +2 -2
  24. package/build/i18n/ja.json +2 -2
  25. package/build/i18n/nl.json +2 -2
  26. package/build/i18n/pl.json +2 -2
  27. package/build/i18n/pt.json +2 -2
  28. package/build/i18n/ro.json +2 -2
  29. package/build/i18n/ru.json +2 -2
  30. package/build/i18n/zh_HK.json +2 -2
  31. package/build/index.js +6 -0
  32. package/build/main.js +568 -499
  33. package/build/main.mjs +418 -349
  34. package/build/stories/ButtonLoader.story.js +109 -0
  35. package/build/stories/ContainerQueries.story.js +74 -0
  36. package/build/stories/DynamicFlow.story.js +37 -0
  37. package/build/stories/DynamicFlowWithRef.story.js +72 -0
  38. package/build/stories/Editable.story.js +58 -0
  39. package/build/stories/Initialisation.story.js +33 -0
  40. package/build/stories/PinnedButtons.story.js +94 -0
  41. package/build/stories/SCAIntegration.story.js +67 -0
  42. package/build/stories/utils/fixtureHttpClient.js +59 -0
  43. package/build/test-utils/NeptuneProviders.js +11 -0
  44. package/build/test-utils/index.js +4 -0
  45. package/build/test-utils/respond-with.js +6 -0
  46. package/build/test-utils/rtl-utils.js +7 -0
  47. package/build/test-utils/wait.js +3 -0
  48. package/build/tests/DynamicFlow.test.js +177 -0
  49. package/build/tests/HideStepTitle.test.js +58 -0
  50. package/build/tests/RequestCache.test.js +34 -0
  51. package/build/tests/SCARejectionHandling.test.js +71 -0
  52. package/build/tests/Subflow.Wise.test.js +650 -0
  53. package/build/tests/events.test.js +124 -0
  54. package/build/tests/logging.test.js +12 -0
  55. package/build/tests/onNotification.test.js +96 -0
  56. package/build/tests/renderers/ControlAndTags.test.js +225 -0
  57. package/build/tests/renderers/ImageRenderer.test.js +129 -0
  58. package/build/tests/renderers/RadioInputRendererComponent.test.js +89 -0
  59. package/build/tests/renderers/ReviewRenderer.test.js +41 -0
  60. package/build/tests/renderers/TextInputRenderer.test.js +245 -0
  61. package/build/tests/renderers/TimeZoneHeader.test.js +19 -0
  62. package/build/tests/renderers/UploadInputRenderer.maxSize.test.js +22 -0
  63. package/build/tests/renderers/value-utils.test.js +24 -0
  64. package/build/tsconfig.types.tsbuildinfo +1 -1
  65. package/package.json +6 -5
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import ReviewRenderer from '@wise/dynamic-flow-renderers/src/ReviewRenderer';
5
+ import { vi } from 'vitest';
6
+ import { renderWithProviders } from '../../test-utils/rtl-utils';
7
+ const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
8
+ const ReviewRendererComponent = ReviewRenderer.render;
9
+ describe('ReviewRenderer', () => {
10
+ const getProps = (fields) => ({
11
+ type: 'review',
12
+ uid: 'uid',
13
+ fields,
14
+ margin: 'md',
15
+ render: () => null,
16
+ stepLoadingState: 'idle',
17
+ features: {},
18
+ trackEvent: vi.fn(),
19
+ httpClient: vi.fn(),
20
+ });
21
+ describe('when help is provided in a field', () => {
22
+ const props = getProps([
23
+ {
24
+ label: 'Field Label',
25
+ value: 'Field Value',
26
+ help: 'Field Help',
27
+ analyticsId: 'review-field-aid',
28
+ },
29
+ ]);
30
+ describe('when clicking the help icon', () => {
31
+ it('should call trackEvent with the correct parameters', async () => {
32
+ renderWithProviders(_jsx(ReviewRendererComponent, Object.assign({}, props)));
33
+ const infoButton = screen.getByRole('button', { name: 'Click here for more info.' });
34
+ await user.click(infoButton);
35
+ expect(props.trackEvent).toHaveBeenCalledWith('Help Pressed', {
36
+ layoutItemId: 'review-field-aid',
37
+ });
38
+ });
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,245 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { fireEvent, screen } from '@testing-library/react';
3
+ import { userEvent } from '@testing-library/user-event';
4
+ import TextInputRenderer from '@wise/dynamic-flow-renderers/src/TextInputRenderer';
5
+ import { useState } from 'react';
6
+ import { vi } from 'vitest';
7
+ import { renderWithProviders } from '../../test-utils/rtl-utils';
8
+ const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
9
+ const TextInputRendererFunction = TextInputRenderer.render;
10
+ describe('TextInputRenderer', () => {
11
+ const props = {
12
+ type: 'input-text',
13
+ autoComplete: 'off',
14
+ control: 'numeric',
15
+ disabled: false,
16
+ uid: 'uid',
17
+ id: 'uid',
18
+ title: 'UK sort code',
19
+ placeholder: '40-30-20',
20
+ required: true,
21
+ value: null,
22
+ displayFormat: '**-**-**',
23
+ maxLength: 8,
24
+ minLength: 6,
25
+ render: () => null,
26
+ trackEvent: vi.fn(),
27
+ stepLoadingState: 'idle',
28
+ httpClient: vi.fn(),
29
+ validationState: undefined,
30
+ fieldLoadingState: 'idle',
31
+ features: {},
32
+ onBlur: vi.fn(),
33
+ onFocus: vi.fn(),
34
+ onChange: vi.fn(),
35
+ };
36
+ describe('when focused', () => {
37
+ it('does not trigger onChange', async () => {
38
+ const onChange = vi.fn();
39
+ renderWithProviders(_jsx(TextInputRendererFunction, Object.assign({}, props, { onChange: onChange })));
40
+ const input = screen.getByLabelText('UK sort code');
41
+ await user.click(input);
42
+ expect(onChange).not.toHaveBeenCalled();
43
+ });
44
+ });
45
+ it('masks the ssn-input value using its display format', async () => {
46
+ const onChange = vi.fn();
47
+ function ControlledInput() {
48
+ const [value, setValue] = useState('');
49
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => {
50
+ onChange(newValue);
51
+ setValue(newValue !== null && newValue !== void 0 ? newValue : '');
52
+ } })));
53
+ }
54
+ renderWithProviders(_jsx(ControlledInput, {}));
55
+ const input = screen.getByLabelText('UK sort code');
56
+ await user.type(input, '123456789');
57
+ expect(input).toHaveAttribute('type', 'text');
58
+ expect(input).toHaveValue('•••-•••-•••');
59
+ });
60
+ it('masks an SSN using the US display format', async () => {
61
+ function ControlledInput() {
62
+ const [value, setValue] = useState('');
63
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-**-****", onChange: (newValue) => setValue(newValue !== null && newValue !== void 0 ? newValue : '') })));
64
+ }
65
+ renderWithProviders(_jsx(ControlledInput, {}));
66
+ const input = screen.getByLabelText('UK sort code');
67
+ await user.type(input, '123456789');
68
+ expect(input).toHaveValue('•••-••-••••');
69
+ });
70
+ it('can temporarily show the ssn-input value', async () => {
71
+ function ControlledInput() {
72
+ const [value, setValue] = useState('123456789');
73
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => setValue(newValue !== null && newValue !== void 0 ? newValue : '') })));
74
+ }
75
+ renderWithProviders(_jsx(ControlledInput, {}));
76
+ const input = screen.getByLabelText('UK sort code');
77
+ const toggle = screen.getByRole('button', { name: 'Show value' });
78
+ expect(input).toHaveValue('•••-•••-•••');
79
+ await user.click(toggle);
80
+ expect(input).toHaveValue('123-456-789');
81
+ expect(screen.getByRole('button', { name: 'Hide value' })).toBeInTheDocument();
82
+ await user.click(screen.getByRole('button', { name: 'Hide value' }));
83
+ expect(input).toHaveValue('•••-•••-•••');
84
+ });
85
+ it('does not treat a formatted non-ssn input as a masked numeric input', async () => {
86
+ function ControlledInput() {
87
+ const [value, setValue] = useState('');
88
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: undefined, displayFormat: "**** ****", onChange: (newValue) => setValue(newValue !== null && newValue !== void 0 ? newValue : '') })));
89
+ }
90
+ renderWithProviders(_jsx(ControlledInput, {}));
91
+ await user.type(screen.getByLabelText('UK sort code'), 'DE12');
92
+ expect(screen.getByLabelText('UK sort code')).toHaveValue('DE12');
93
+ });
94
+ it('handles a native value change for ssn-input', () => {
95
+ function ControlledInput() {
96
+ const [value, setValue] = useState('');
97
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => setValue(newValue !== null && newValue !== void 0 ? newValue : '') })));
98
+ }
99
+ renderWithProviders(_jsx(ControlledInput, {}));
100
+ const input = screen.getByLabelText('UK sort code');
101
+ fireEvent.change(input, { target: { value: '123456789' } });
102
+ expect(input).toHaveValue('•••-•••-•••');
103
+ });
104
+ it('replaces a selected ssn-input value when typing', async () => {
105
+ const onChange = vi.fn();
106
+ function ControlledInput() {
107
+ const [value, setValue] = useState('123456789');
108
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => {
109
+ onChange(newValue);
110
+ setValue(newValue !== null && newValue !== void 0 ? newValue : '');
111
+ } })));
112
+ }
113
+ renderWithProviders(_jsx(ControlledInput, {}));
114
+ const input = screen.getByLabelText('UK sort code');
115
+ input.setSelectionRange(0, input.value.length);
116
+ input.focus();
117
+ await user.keyboard('987654321');
118
+ expect(onChange).toHaveBeenLastCalledWith('987654321');
119
+ expect(input).toHaveValue('•••-•••-•••');
120
+ });
121
+ it('replaces a selected ssn-input value when pasting', () => {
122
+ const onChange = vi.fn();
123
+ function ControlledInput() {
124
+ const [value, setValue] = useState('123456789');
125
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => {
126
+ onChange(newValue);
127
+ setValue(newValue !== null && newValue !== void 0 ? newValue : '');
128
+ } })));
129
+ }
130
+ renderWithProviders(_jsx(ControlledInput, {}));
131
+ const input = screen.getByLabelText('UK sort code');
132
+ input.setSelectionRange(0, input.value.length);
133
+ fireEvent.paste(input, {
134
+ clipboardData: { getData: () => '987654321' },
135
+ });
136
+ expect(onChange).toHaveBeenLastCalledWith('987654321');
137
+ expect(input).toHaveValue('•••-•••-•••');
138
+ });
139
+ it('allows typing beyond the display format without dropping trailing digits', async () => {
140
+ const onChange = vi.fn();
141
+ function ControlledInput() {
142
+ const [value, setValue] = useState('123456789');
143
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => {
144
+ onChange(newValue);
145
+ setValue(newValue !== null && newValue !== void 0 ? newValue : '');
146
+ } })));
147
+ }
148
+ renderWithProviders(_jsx(ControlledInput, {}));
149
+ const input = screen.getByLabelText('UK sort code');
150
+ input.setSelectionRange(1, 1);
151
+ input.focus();
152
+ await user.keyboard('0');
153
+ expect(onChange).toHaveBeenLastCalledWith('1023456789');
154
+ expect(input).toHaveValue('•••-•••-••••');
155
+ });
156
+ it('preserves the suffix when pasting beyond the display format', () => {
157
+ const onChange = vi.fn();
158
+ function ControlledInput() {
159
+ const [value, setValue] = useState('123456');
160
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => {
161
+ onChange(newValue);
162
+ setValue(newValue !== null && newValue !== void 0 ? newValue : '');
163
+ } })));
164
+ }
165
+ renderWithProviders(_jsx(ControlledInput, {}));
166
+ const input = screen.getByLabelText('UK sort code');
167
+ input.setSelectionRange(1, 1);
168
+ fireEvent.paste(input, {
169
+ clipboardData: { getData: () => '987654321' },
170
+ });
171
+ expect(onChange).toHaveBeenLastCalledWith('198765432123456');
172
+ expect(input.value).toHaveLength(17);
173
+ });
174
+ it('restores the caret after a partial keyboard replacement', async () => {
175
+ const onChange = vi.fn();
176
+ function ControlledInput() {
177
+ const [value, setValue] = useState('12345');
178
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => {
179
+ onChange(newValue);
180
+ setValue(newValue !== null && newValue !== void 0 ? newValue : '');
181
+ } })));
182
+ }
183
+ renderWithProviders(_jsx(ControlledInput, {}));
184
+ const input = screen.getByLabelText('UK sort code');
185
+ input.setSelectionRange(2, 3);
186
+ input.focus();
187
+ await user.keyboard('98');
188
+ expect(onChange).toHaveBeenLastCalledWith('129845');
189
+ expect(input).toHaveValue('•••-•••');
190
+ expect(input.selectionStart).toBe(5);
191
+ expect(input.selectionEnd).toBe(5);
192
+ });
193
+ it('restores the caret after a same-length paste', () => {
194
+ const onChange = vi.fn();
195
+ function ControlledInput() {
196
+ const [value, setValue] = useState('12345');
197
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => {
198
+ onChange(newValue);
199
+ setValue(newValue !== null && newValue !== void 0 ? newValue : '');
200
+ } })));
201
+ }
202
+ renderWithProviders(_jsx(ControlledInput, {}));
203
+ const input = screen.getByLabelText('UK sort code');
204
+ input.setSelectionRange(2, 3);
205
+ fireEvent.paste(input, {
206
+ clipboardData: { getData: () => '9' },
207
+ });
208
+ expect(onChange).toHaveBeenLastCalledWith('12945');
209
+ expect(input.selectionStart).toBe(3);
210
+ expect(input.selectionEnd).toBe(3);
211
+ });
212
+ it('restores the caret when a paste leaves the raw value unchanged', () => {
213
+ const onChange = vi.fn();
214
+ function ControlledInput() {
215
+ const [value, setValue] = useState('12345');
216
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", displayFormat: "***-***-***", onChange: (newValue) => {
217
+ onChange(newValue);
218
+ setValue(newValue !== null && newValue !== void 0 ? newValue : '');
219
+ } })));
220
+ }
221
+ renderWithProviders(_jsx(ControlledInput, {}));
222
+ const input = screen.getByLabelText('UK sort code');
223
+ input.setSelectionRange(2, 3);
224
+ fireEvent.paste(input, {
225
+ clipboardData: { getData: () => '3' },
226
+ });
227
+ expect(input.selectionStart).toBe(3);
228
+ expect(input.selectionEnd).toBe(3);
229
+ fireEvent.keyDown(input, { key: 'Backspace' });
230
+ expect(onChange).toHaveBeenLastCalledWith('1245');
231
+ });
232
+ it('submits the unformatted value for the ssn-input control', async () => {
233
+ const onChange = vi.fn();
234
+ function ControlledInput() {
235
+ const [value, setValue] = useState('');
236
+ return (_jsx(TextInputRendererFunction, Object.assign({}, props, { value: value, control: "ssn-input", onChange: (newValue) => {
237
+ onChange(newValue);
238
+ setValue(newValue !== null && newValue !== void 0 ? newValue : '');
239
+ } })));
240
+ }
241
+ renderWithProviders(_jsx(ControlledInput, {}));
242
+ await user.type(screen.getByLabelText('UK sort code'), '123456');
243
+ expect(onChange).toHaveBeenLastCalledWith('123456');
244
+ });
245
+ });
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { vi } from 'vitest';
3
+ import { DynamicFlow } from '../../dynamicFlow/DynamicFlow';
4
+ import { renderWithProviders } from '../../test-utils/rtl-utils';
5
+ describe('TimeZoneHeader', () => {
6
+ it('includes a time-zone header in all requests', () => {
7
+ const props = {
8
+ flowId: 'test',
9
+ initialAction: { url: '/api/test', method: 'GET' },
10
+ customFetch: vi.fn(),
11
+ onCompletion: vi.fn(),
12
+ onError: vi.fn(),
13
+ };
14
+ renderWithProviders(_jsx(DynamicFlow, Object.assign({}, props)));
15
+ expect(props.customFetch).toHaveBeenCalledWith('/api/test', expect.anything());
16
+ const requestHeaders = new Headers(props.customFetch.mock.calls[0][1].headers);
17
+ expect(requestHeaders.get('Time-Zone')).toBeDefined();
18
+ });
19
+ });
@@ -0,0 +1,22 @@
1
+ import { UploadInputRenderer } from '@wise/dynamic-flow-renderers/src/UploadInputRenderer';
2
+ import { vi } from 'vitest';
3
+ const rendererFunction = UploadInputRenderer.render;
4
+ const getProps = (overrides = {}) => (Object.assign({ type: 'input-upload', uid: 'test-upload', id: 'test-upload', title: 'Upload File', description: 'Please upload your file', disabled: false, source: 'file', accepts: ['image/*'], maxSize: undefined, value: null, required: false, stepLoadingState: 'idle', validationState: { status: 'valid' }, fieldLoadingState: 'idle', features: {}, onUpload: vi.fn(), render: vi.fn(), httpClient: vi.fn(), trackEvent: vi.fn(), onBlur: vi.fn(), onFocus: vi.fn() }, overrides));
5
+ describe('UploadInputRenderer maxSize', () => {
6
+ describe('when maxSize is set', () => {
7
+ it('should pass maxSize to UploadInput', () => {
8
+ const props = getProps({ maxSize: 5120000 }); // 5 MB
9
+ const renderedComponent = rendererFunction(props);
10
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
11
+ expect(renderedComponent === null || renderedComponent === void 0 ? void 0 : renderedComponent.props.children.props.sizeLimit).toBe(5000); // 5 MB in KB
12
+ });
13
+ });
14
+ describe('when maxSize is not set', () => {
15
+ it('should pass null to UploadInput', () => {
16
+ const props = getProps();
17
+ const renderedComponent = rendererFunction(props);
18
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
19
+ expect(renderedComponent === null || renderedComponent === void 0 ? void 0 : renderedComponent.props.children.props.sizeLimit).toBeNull();
20
+ });
21
+ });
22
+ });
@@ -0,0 +1,24 @@
1
+ import { dateStringToDateOrNull, dateToDateString } from '@wise/dynamic-flow-renderers/src/utils/value-utils';
2
+ describe('dateStringToDateOrNull', () => {
3
+ it('should return null if the input is nullish', () => {
4
+ expect(dateStringToDateOrNull(undefined)).toBeNull();
5
+ expect(dateStringToDateOrNull('')).toBeNull();
6
+ expect(dateStringToDateOrNull(null)).toBeNull();
7
+ });
8
+ it('should return null if the input is not a valid date', () => {
9
+ expect(dateStringToDateOrNull('Jan-06-2022')).toBeNull();
10
+ expect(dateStringToDateOrNull('Jan 06 2022')).toBeNull();
11
+ });
12
+ it('should return a Date object if the input is a valid date string', () => {
13
+ const date = dateStringToDateOrNull('2022-01-31');
14
+ expect(date === null || date === void 0 ? void 0 : date.getFullYear()).toBe(2022);
15
+ expect(date === null || date === void 0 ? void 0 : date.getMonth()).toBe(0);
16
+ expect(date === null || date === void 0 ? void 0 : date.getDate()).toBe(31);
17
+ });
18
+ });
19
+ describe('dateToDateString', () => {
20
+ it('should return a formatted date string when passed a date', () => {
21
+ const date = new Date('2022-01-01');
22
+ expect(dateToDateString(date)).toBe('2022-01-01');
23
+ });
24
+ });