@wise/dynamic-flow-client-internal 5.31.0-experimental-a2135d6 → 5.31.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 (56) 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/en.json +0 -2
  21. package/build/i18n/index.js +43 -0
  22. package/build/index.js +6 -0
  23. package/build/main.js +67 -296
  24. package/build/main.mjs +56 -286
  25. package/build/stories/ButtonLoader.story.js +109 -0
  26. package/build/stories/ContainerQueries.story.js +74 -0
  27. package/build/stories/DynamicFlow.story.js +37 -0
  28. package/build/stories/DynamicFlowWithRef.story.js +72 -0
  29. package/build/stories/Editable.story.js +58 -0
  30. package/build/stories/Initialisation.story.js +33 -0
  31. package/build/stories/PinnedButtons.story.js +94 -0
  32. package/build/stories/SCAIntegration.story.js +67 -0
  33. package/build/stories/utils/fixtureHttpClient.js +59 -0
  34. package/build/test-utils/NeptuneProviders.js +11 -0
  35. package/build/test-utils/index.js +4 -0
  36. package/build/test-utils/respond-with.js +6 -0
  37. package/build/test-utils/rtl-utils.js +7 -0
  38. package/build/test-utils/wait.js +3 -0
  39. package/build/tests/DynamicFlow.test.js +177 -0
  40. package/build/tests/HideStepTitle.test.js +58 -0
  41. package/build/tests/RequestCache.test.js +34 -0
  42. package/build/tests/SCARejectionHandling.test.js +71 -0
  43. package/build/tests/Subflow.Wise.test.js +650 -0
  44. package/build/tests/events.test.js +124 -0
  45. package/build/tests/logging.test.js +12 -0
  46. package/build/tests/onNotification.test.js +96 -0
  47. package/build/tests/renderers/ControlAndTags.test.js +225 -0
  48. package/build/tests/renderers/ImageRenderer.test.js +129 -0
  49. package/build/tests/renderers/RadioInputRendererComponent.test.js +89 -0
  50. package/build/tests/renderers/ReviewRenderer.test.js +41 -0
  51. package/build/tests/renderers/TextInputRenderer.test.js +44 -0
  52. package/build/tests/renderers/TimeZoneHeader.test.js +19 -0
  53. package/build/tests/renderers/UploadInputRenderer.maxSize.test.js +22 -0
  54. package/build/tests/renderers/value-utils.test.js +24 -0
  55. package/build/tsconfig.types.tsbuildinfo +1 -1
  56. package/package.json +4 -4
@@ -0,0 +1,72 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { action } from 'storybook/actions';
3
+ import { Button } from '@transferwise/components';
4
+ import { useRef, useState } from 'react';
5
+ import { DynamicFlowWithRef } from '..';
6
+ import { fixtureHttpClient } from './utils/fixtureHttpClient';
7
+ const step = {
8
+ id: 'dynamic-form-story',
9
+ title: 'Dynamic Form',
10
+ schemas: [
11
+ {
12
+ $id: '#form',
13
+ type: 'object',
14
+ displayOrder: ['name', 'city'],
15
+ required: ['name'],
16
+ properties: {
17
+ name: { type: 'string', title: 'Name', minLength: 2 },
18
+ city: {
19
+ title: 'City',
20
+ oneOf: [
21
+ { title: 'London', const: 'London', icon: { name: 'flag-gb' } },
22
+ { title: 'Paris', const: 'Paris', icon: { name: 'flag-fr' } },
23
+ { title: 'New York', const: 'New York', icon: { name: 'flag-us' } },
24
+ ],
25
+ },
26
+ },
27
+ },
28
+ ],
29
+ layout: [
30
+ {
31
+ type: 'form',
32
+ schemaId: '#form',
33
+ },
34
+ ],
35
+ };
36
+ function DynamicFlowWithRefStory() {
37
+ const ref = useRef(null);
38
+ const props = {
39
+ flowId: 'storybook',
40
+ analyticsId: 'storybook',
41
+ customFetch: fixtureHttpClient,
42
+ onValueChange: action('onValueChange'),
43
+ onError: action('onError'),
44
+ onEvent: action('onEvent'),
45
+ onLog: action('onLog'),
46
+ };
47
+ const [currentValue, setCurrentValue] = useState('');
48
+ const [isValid, setValid] = useState(null);
49
+ const backgroundColor = isValid === null ? '#eeeeee' : isValid ? '#eeffee' : '#ffeeee';
50
+ const validityText = isValid === null ? '' : isValid ? '✅ valid\n\n' : '❌ invalid\n\n';
51
+ return (_jsxs(_Fragment, { children: [_jsx(DynamicFlowWithRef, Object.assign({ ref: ref }, props, { initialStep: step, onValueChange: async () => {
52
+ var _a;
53
+ var _b;
54
+ const newValue = (_b = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.getValue()) !== null && _b !== void 0 ? _b : null;
55
+ setCurrentValue(JSON.stringify(newValue, null, 2));
56
+ }, onCompletion: () => { } }), JSON.stringify(step)), _jsx(Button, { onClick: async () => {
57
+ var _a;
58
+ var _b;
59
+ const ok = (_b = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.validate()) !== null && _b !== void 0 ? _b : false;
60
+ setValid(ok);
61
+ }, children: "validate()" }), _jsx("hr", {}), _jsxs("pre", { style: { backgroundColor }, children: [validityText, currentValue] })] }));
62
+ }
63
+ const meta = {
64
+ title: 'Dynamic Form',
65
+ component: DynamicFlowWithRefStory,
66
+ };
67
+ export const AllFixtures = {
68
+ parameters: {
69
+ chromatic: { disableSnapshot: true },
70
+ },
71
+ };
72
+ export default meta;
@@ -0,0 +1,58 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { action } from 'storybook/actions';
3
+ import { layouts, schemas } from '@wise/dynamic-flow-fixtures';
4
+ import { useMemo, useState } from 'react';
5
+ import { DynamicFlow } from '../dynamicFlow/DynamicFlow';
6
+ const fixtures = Object.assign(Object.assign({}, schemas), layouts);
7
+ export function Editable({ initialStepName = '' }) {
8
+ const selectedStep = fixtures[initialStepName] || {};
9
+ const selectedInitialStep = JSON.stringify(selectedStep, null, 2);
10
+ return _jsx(InteractiveDemo, { selectedInitialStep: selectedInitialStep }, selectedInitialStep);
11
+ }
12
+ function InteractiveDemo({ selectedInitialStep }) {
13
+ const [initialStatusCode, setInitialStatusCode] = useState(200);
14
+ const [initialStep, setInitialStep] = useState(selectedInitialStep);
15
+ const onTextAreaChange = (event) => {
16
+ setInitialStep(event.target.value);
17
+ };
18
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("h3", { children: "Editable Demo" }), _jsx("p", { children: "You can specify a response status code and body, and DF will load your step below." }), _jsxs("fieldset", { children: [_jsx("label", { htmlFor: "status-code", children: "Response Status code: " }), _jsx(StatusCodeSelect, { id: "status-code", state: [initialStatusCode, setInitialStatusCode] })] }), _jsxs("fieldset", { children: [_jsx("label", { htmlFor: "textarea", children: "Response Body:" }), _jsx(InvalidJsonWarning, { initialStep: initialStep }), _jsx("textarea", { id: "textarea", wrap: "off", rows: 40, style: {
19
+ fontFamily: 'monospace',
20
+ fontSize: '0.9em',
21
+ width: '100%',
22
+ maxHeight: '200px',
23
+ }, value: initialStep, onChange: onTextAreaChange })] })] }), isValidJson(initialStep) ? (_jsx(DynamicFlow, { flowId: "storybook", customFetch: fetch, initialStep: JSON.parse(initialStep), onCompletion: action('onCompletion'), onError: (error) => {
24
+ action('onError')(error);
25
+ console.error(error);
26
+ } }, `${initialStep}-${initialStatusCode}`)) : null] }));
27
+ }
28
+ function InvalidJsonWarning({ initialStep }) {
29
+ const validJson = useMemo(() => isValidJson(initialStep), [initialStep]);
30
+ return !validJson ? (_jsx("p", { children: "\u274C Invalid syntax or missing elements are preventing the JSON code you entered from being parsed." })) : null;
31
+ }
32
+ const isValidJson = (value) => {
33
+ try {
34
+ JSON.parse(value);
35
+ return true;
36
+ }
37
+ catch (_a) {
38
+ return false;
39
+ }
40
+ };
41
+ function StatusCodeSelect({ id, state: [value, setValue], }) {
42
+ return (_jsxs("select", { id: id, value: value, onChange: (event) => setValue(Number.parseInt(event.target.value, 10)), children: [_jsx("option", { children: "200" }), _jsx("option", { children: "400" }), _jsx("option", { children: "500" })] }));
43
+ }
44
+ export default {
45
+ component: Editable,
46
+ title: 'Multi-fixture',
47
+ parameters: {
48
+ chromatic: { disableSnapshot: true },
49
+ },
50
+ argTypes: {
51
+ initialStepName: {
52
+ label: 'Initial Step',
53
+ control: 'select',
54
+ options: Object.keys(fixtures),
55
+ },
56
+ },
57
+ args: { initialStepName: Object.keys(fixtures)[0] },
58
+ };
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { action } from 'storybook/actions';
3
+ import { DynamicFlow } from '../dynamicFlow/DynamicFlow';
4
+ export default {
5
+ title: 'Initialisation',
6
+ component: DynamicFlow,
7
+ };
8
+ const mockFetch = async (url, init) => {
9
+ action('mockFetch')(url, init);
10
+ switch (url) {
11
+ case '/first-step':
12
+ return new Response(JSON.stringify({
13
+ id: 'first-step',
14
+ title: 'First Step',
15
+ layout: [],
16
+ schemas: [],
17
+ }));
18
+ default:
19
+ return new Response(null, { status: 404 });
20
+ }
21
+ };
22
+ const onCompletion = action('onCompletion');
23
+ const onError = action('onError');
24
+ const onEvent = action('onEvent');
25
+ const onLog = action('onLog');
26
+ const initialActionValid = { method: 'GET', url: '/first-step' };
27
+ export const InitialisationWithSuccessfulStep = {
28
+ render: () => (_jsx(DynamicFlow, { flowId: "initialisation-telemetry", customFetch: mockFetch, initialAction: initialActionValid, onCompletion: onCompletion, onError: onError, onEvent: onEvent, onLog: onLog })),
29
+ };
30
+ const initialActionInvalid = { method: 'GET', url: '/invalid-step' };
31
+ export const InitialisationWith404Step = {
32
+ render: () => (_jsx(DynamicFlow, { flowId: "initialisation-telemetry", customFetch: mockFetch, initialAction: initialActionInvalid, onCompletion: onCompletion, onError: onError, onEvent: onEvent, onLog: onLog })),
33
+ };
@@ -0,0 +1,94 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { action } from 'storybook/actions';
3
+ import { Modal, Button } from '@transferwise/components';
4
+ import { useState } from 'react';
5
+ import { DynamicFlow } from '../dynamicFlow/DynamicFlow';
6
+ const httpClient = async (input, init) => {
7
+ action('httpClient')(input, init);
8
+ return fetch(input, init);
9
+ };
10
+ export default {
11
+ component: DynamicFlow,
12
+ title: 'Revamp/Features/Pinned Buttons',
13
+ parameters: {
14
+ chromatic: { disableSnapshot: true },
15
+ },
16
+ };
17
+ export function Basic() {
18
+ return _jsx(DynamicFlow, Object.assign({}, defaultProps, { initialStep: getStep(20) }));
19
+ }
20
+ export function BasicMobileWeb() {
21
+ return _jsx(DynamicFlow, Object.assign({}, defaultProps, { initialStep: getStep(20) }));
22
+ }
23
+ BasicMobileWeb.globals = {
24
+ viewport: { value: 'mobile2' },
25
+ };
26
+ export function InAModal() {
27
+ const [open, setOpen] = useState(true);
28
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { onClick: () => setOpen(true), children: "Open Modal" }), _jsx(Modal, { title: "Modal Title", open: open, body: _jsx(DynamicFlow, Object.assign({}, defaultProps, { initialStep: getStep(10) })), onClose: () => setOpen(false) })] }));
29
+ }
30
+ export function InAModalMobileWeb() {
31
+ const [open, setOpen] = useState(true);
32
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { onClick: () => setOpen(true), children: "Open Modal (large phone)" }), _jsx(Modal, { title: "Modal Title", open: open, body: _jsx(DynamicFlow, Object.assign({}, defaultProps, { initialStep: getStep(10) })), onClose: () => setOpen(false) })] }));
33
+ }
34
+ InAModalMobileWeb.globals = {
35
+ viewport: { value: 'mobile2' },
36
+ };
37
+ export function InAModalInAModal() {
38
+ const [openFirst, setOpenFirst] = useState(true);
39
+ const [openSecond, setOpenSecond] = useState(true);
40
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { onClick: () => setOpenFirst(true), children: "Open First Modal" }), _jsx(Modal, { title: "First Modal", open: openFirst, body: _jsx(Button, { onClick: () => setOpenSecond(true), children: "Open Second Modal" }), scroll: "content", onClose: () => setOpenFirst(false) }), _jsx(Modal, { title: "Second Modal", open: openSecond, body: _jsx(DynamicFlow, Object.assign({}, defaultProps, { initialStep: getStep(10) })), scroll: "content", onClose: () => setOpenSecond(false) })] }));
41
+ }
42
+ const defaultProps = {
43
+ flowId: 'storybook',
44
+ httpClient,
45
+ loaderConfig: { size: 'xl', initial: true, submission: false },
46
+ features: {
47
+ pinnedButtons: true,
48
+ },
49
+ onCompletion: action('onCompletion'),
50
+ onError: action('onError'),
51
+ onEvent: action('onEvent'),
52
+ onLog: action('onLog'),
53
+ };
54
+ const getStep = (numberOfParagraphs) => ({
55
+ id: 'id',
56
+ title: 'Heading',
57
+ schemas: [],
58
+ layout: [
59
+ ...Array.from({ length: numberOfParagraphs }, () => ({
60
+ text: 'Lorem ipsum lorem ipsum amet, consectetur adipiscing elit. Toolbarum buttonis dynamicus flowus adaptare semper est laboriosum. Frustratio crescit cum mutationibus constantibus, et semper nova repositione buttonorum. Complexitas crescit, et usus seamless conservare fit arduum, quasi pugna contra interfaciem mutantem. Lorem ipsum lorem ipsum amet, consectetur adipiscing elit. Toolbarum buttonis dynamicus flowus adaptare semper est laboriosum. Frustratio crescit cum mutationibus constantibus, et semper nova repositione buttonorum. Complexitas crescit, et usus seamless conservare fit arduum, quasi pugna contra interfaciem mutantem. Lorem ipsum lorem ipsum amet, consectetur adipiscing elit. Toolbarum buttonis dynamicus flowus adaptare semper est laboriosum. Frustratio crescit cum mutationibus constantibus, et semper nova repositione buttonorum. Complexitas crescit, et usus seamless conservare fit arduum, quasi pugna contra interfaciem mutantem. Lorem ipsum lorem ipsum amet, consectetur adipiscing elit. Toolbarum buttonis dynamicus flowus adaptare semper est laboriosum. Frustratio crescit cum mutationibus constantibus, et semper nova repositione buttonorum. Complexitas crescit, et usus seamless conservare fit arduum, quasi pugna contra interfaciem mutantem.',
61
+ type: 'paragraph',
62
+ })),
63
+ {
64
+ type: 'button',
65
+ title: 'I am the first pinned button',
66
+ control: 'primary',
67
+ action: {
68
+ url: '/v2/accounts',
69
+ method: 'POST',
70
+ },
71
+ pinOrder: 0,
72
+ },
73
+ {
74
+ type: 'button',
75
+ title: 'I am the second',
76
+ context: 'negative',
77
+ control: 'secondary',
78
+ action: {
79
+ url: '/v2/accounts',
80
+ method: 'POST',
81
+ },
82
+ pinOrder: 1,
83
+ },
84
+ {
85
+ type: 'button',
86
+ title: 'I am not pinned',
87
+ control: 'tertiary',
88
+ action: {
89
+ url: '/v2/accounts',
90
+ method: 'POST',
91
+ },
92
+ },
93
+ ],
94
+ });
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /* eslint-disable no-console */
3
+ import { action } from 'storybook/actions';
4
+ import { DynamicFlow } from '../index';
5
+ import { useCallback } from 'react';
6
+ export default {
7
+ component: DynamicFlow,
8
+ title: 'Dev Tools/Features',
9
+ parameters: {
10
+ chromatic: { disableSnapshot: true },
11
+ },
12
+ };
13
+ export function SCAIntegration() {
14
+ // const [isModalOpen, setIsModalOpen] = useState(false);
15
+ const customFetch = useCallback(async (input, init) => {
16
+ action('httpClient')(input, init === null || init === void 0 ? void 0 : init.body);
17
+ await wait(250);
18
+ // simulate SCARequest rejecting when user closes the modal
19
+ throw new Error('SCA_MODAL_CLOSED');
20
+ }, []);
21
+ return (_jsx(DynamicFlow, { flowId: "new-df-flow-id", customFetch: customFetch, initialStep: initialStep, onCompletion: action('onCompletion'), onEvent: action('onEvent'), onLog: action('onLog'), onError: (error) => {
22
+ action('onError');
23
+ console.error(error);
24
+ } }));
25
+ }
26
+ const wait = async (ms) => {
27
+ return new Promise((resolve) => {
28
+ setTimeout(() => {
29
+ resolve(true);
30
+ }, ms);
31
+ });
32
+ };
33
+ const initialStep = {
34
+ id: 'step-id',
35
+ title: 'Enter your email',
36
+ description: "When you submit we'll simulate the SCARequest rejecting with 'SCA_MODAL_CLOSED'",
37
+ schemas: [
38
+ {
39
+ $id: '#schema',
40
+ type: 'object',
41
+ displayOrder: ['key'],
42
+ properties: {
43
+ key: {
44
+ minLength: 4,
45
+ title: 'Email',
46
+ control: 'email',
47
+ type: 'string',
48
+ },
49
+ },
50
+ },
51
+ ],
52
+ layout: [
53
+ {
54
+ type: 'paragraph',
55
+ text: 'There should be no error message in this page after submission.',
56
+ align: 'center',
57
+ },
58
+ { schemaId: '#schema', type: 'form' },
59
+ {
60
+ type: 'button',
61
+ title: 'Continue',
62
+ action: { url: '/submit' },
63
+ control: 'primary',
64
+ },
65
+ ],
66
+ navigation: { back: { title: 'Back', action: { url: '/go-back', method: 'GET' } } },
67
+ };
@@ -0,0 +1,59 @@
1
+ import { action } from 'storybook/actions';
2
+ import { layouts, schemas, responses } from '@wise/dynamic-flow-fixtures';
3
+ export const getObjectKeys = Object.keys;
4
+ const fixtures = Object.assign(Object.assign({}, schemas), layouts);
5
+ const isObject = (val) => typeof val === 'object' && val !== null;
6
+ export const fixtureHttpClient = async (input, init) => {
7
+ action('httpClient')(input, init);
8
+ const url = getURL(input);
9
+ if (url.pathname.startsWith('/steps/')) {
10
+ return respondWithFixture(fixtures, url, init);
11
+ }
12
+ if (url.pathname.startsWith('/responses/')) {
13
+ return respondWithFixture(responses, url, init);
14
+ }
15
+ if (url.pathname.startsWith('/validate')) {
16
+ return new Response(JSON.stringify({ message: 'Validation-async OK' }));
17
+ }
18
+ return new Response(null, { status: 404 });
19
+ };
20
+ const getURL = (input) => {
21
+ if (input instanceof URL) {
22
+ return input;
23
+ }
24
+ if (input instanceof Request) {
25
+ return new URL(input.url, 'http://foo');
26
+ }
27
+ return new URL(input, 'http://foo');
28
+ };
29
+ const respondWithFixture = async (fixtureList, url, init) => {
30
+ var _a;
31
+ const fixtureNames = getObjectKeys(fixtureList);
32
+ const name = url.pathname
33
+ .split('/')
34
+ .filter((part) => Boolean(part))
35
+ .pop();
36
+ if (fixtureNames.includes(name !== null && name !== void 0 ? name : '')) {
37
+ const fixture = fixtureList[name];
38
+ // by convention if the url contains a "delay" query parameter, we delay the response
39
+ if (url.searchParams.has('delay')) {
40
+ await new Promise((resolve) => {
41
+ setTimeout(resolve, Number(url.searchParams.get('delay')));
42
+ });
43
+ }
44
+ // by convention if the url contains a "refresh"query parameterm we use the payload as model
45
+ if (url.searchParams.has('refresh') &&
46
+ (init === null || init === void 0 ? void 0 : init.body) &&
47
+ typeof init.body === 'string' &&
48
+ isObject(fixture)) {
49
+ const body = JSON.parse(init.body);
50
+ return new Response(JSON.stringify(Object.assign(Object.assign({}, fixture), { model: Object.assign(Object.assign({}, ((_a = fixture.model) !== null && _a !== void 0 ? _a : {})), body) })));
51
+ }
52
+ if (url.pathname.includes('exit')) {
53
+ return new Response(JSON.stringify(fixture), { headers: { 'X-DF-Exit': 'true' } });
54
+ }
55
+ return new Response(JSON.stringify(fixture));
56
+ }
57
+ action('ERROR 404')(url);
58
+ return new Response(null, { status: 404 });
59
+ };
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Provider as ComponentProvider, DEFAULT_LANG, DEFAULT_LOCALE, SnackbarProvider, translations as componentTranslations, getLangFromLocale, } from '@transferwise/components';
3
+ import { ThemeProvider } from '@wise/components-theming';
4
+ import { translations } from '@wise/dynamic-flow-client';
5
+ const locale = DEFAULT_LOCALE;
6
+ const lang = getLangFromLocale(locale) || DEFAULT_LANG;
7
+ const messages = Object.assign(Object.assign({}, componentTranslations[lang]), translations[lang]);
8
+ function NeptuneProviders({ children }) {
9
+ return (_jsx(ThemeProvider, { theme: "personal", screenMode: "light", children: _jsx(SnackbarProvider, { children: _jsx(ComponentProvider, { i18n: { locale, messages }, children: children }) }) }));
10
+ }
11
+ export default NeptuneProviders;
@@ -0,0 +1,4 @@
1
+ export { default as NeptuneProviders } from './NeptuneProviders';
2
+ export { renderWithProviders } from './rtl-utils';
3
+ export { respondWith } from './respond-with';
4
+ export { wait } from './wait';
@@ -0,0 +1,6 @@
1
+ export const respondWith = async (data, init) => new Response(data == null ? null : JSON.stringify(data), init);
2
+ export const respondWithDelay = async (data, delayMs, init) => new Promise((resolve) => {
3
+ setTimeout(() => {
4
+ resolve(new Response(JSON.stringify(data), init));
5
+ }, delayMs);
6
+ });
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from '@testing-library/react';
3
+ import NeptuneProviders from './NeptuneProviders';
4
+ export const renderWithProviders = (children, options) => {
5
+ const renderedResult = render(_jsx(NeptuneProviders, { children: children }), options);
6
+ return Object.assign(Object.assign({}, renderedResult), { rerender: (ui, renderOptions) => renderWithProviders(ui, Object.assign({ container: renderedResult.container }, renderOptions)) });
7
+ };
@@ -0,0 +1,3 @@
1
+ export const wait = async (ms) => new Promise((resolve) => {
2
+ setTimeout(resolve, ms);
3
+ });
@@ -0,0 +1,177 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { screen, waitFor } from '@testing-library/react';
3
+ import { userEvent } from '@testing-library/user-event';
4
+ import { vi } from 'vitest';
5
+ import { DynamicFlow } from '../dynamicFlow/DynamicFlow';
6
+ import { DynamicFlowWithRef } from '../dynamicFlow/DynamicFlowWithRef';
7
+ import { renderWithProviders, wait } from '../test-utils';
8
+ import { DynamicFlowModal } from '../dynamicFlow/DynamicFlowModal';
9
+ const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
10
+ const cases = [
11
+ { name: 'DynamicFlow', DFComponent: DynamicFlow },
12
+ { name: 'DynamicFlowWithRef', DFComponent: DynamicFlowWithRef },
13
+ { name: 'DynamicFlowModal', DFComponent: DynamicFlowModal },
14
+ ];
15
+ describe.each(cases)('Wise-Specific Headers', ({ name, DFComponent }) => {
16
+ it(`${name} - makes an initial request with additional RequestInit parameters, as specified in the wise-specific fetcher`, async () => {
17
+ const props = {
18
+ flowId: 'test',
19
+ initialAction: { url: '/initial' },
20
+ onCompletion: vi.fn(),
21
+ onError: vi.fn(),
22
+ };
23
+ const customFetch = vi.fn();
24
+ renderWithProviders(_jsx(DFComponent, Object.assign({}, props, { customFetch: customFetch })));
25
+ await waitFor(() => {
26
+ expect(customFetch).toHaveBeenCalledWith('/initial', expect.anything());
27
+ });
28
+ const requestInit = customFetch.mock.calls[0][1];
29
+ const credentials = requestInit === null || requestInit === void 0 ? void 0 : requestInit.credentials;
30
+ expect(credentials).toBe('include');
31
+ const headers = Array.from(new Headers(requestInit === null || requestInit === void 0 ? void 0 : requestInit.headers).entries());
32
+ expect(headers).toContainEqual(['accept-language', 'en-GB']);
33
+ expect(headers).toContainEqual(['content-type', 'application/json']);
34
+ });
35
+ });
36
+ describe.each(cases)('Usage of customFetch function', ({ name, DFComponent }) => {
37
+ it(`${name} - uses provided customFetch for image-fetching`, async () => {
38
+ const props = {
39
+ flowId: 'test',
40
+ initialStep: {
41
+ id: 'step-id',
42
+ title: 'Image Step',
43
+ layout: [
44
+ {
45
+ type: 'image',
46
+ url: '/image',
47
+ },
48
+ ],
49
+ schemas: [],
50
+ },
51
+ onCompletion: vi.fn(),
52
+ onError: vi.fn(),
53
+ };
54
+ const customFetch = vi.fn();
55
+ renderWithProviders(_jsx(DFComponent, Object.assign({}, props, { customFetch: customFetch })));
56
+ await waitFor(() => {
57
+ expect(customFetch).toHaveBeenCalledWith('/image', expect.anything());
58
+ });
59
+ });
60
+ });
61
+ describe.each(cases)('onEvent is triggered', ({ name, DFComponent }) => {
62
+ it(`${name} - triggers provided onEvent when an event is emitted`, async () => {
63
+ const props = {
64
+ flowId: 'test',
65
+ initialAction: { url: '/initial' },
66
+ customFetch: vi.fn(),
67
+ onCompletion: vi.fn(),
68
+ onError: vi.fn(),
69
+ };
70
+ const onEvent = vi.fn();
71
+ renderWithProviders(_jsx(DFComponent, Object.assign({}, props, { onEvent: onEvent })));
72
+ await waitFor(() => {
73
+ expect(onEvent).toHaveBeenCalledWith('Dynamic Flow - Initiated', expect.objectContaining({ flowId: 'test', integrationId: 'test' }));
74
+ });
75
+ });
76
+ });
77
+ describe.each(cases)('Renderer Context', ({ name, DFComponent }) => {
78
+ it(`${name} - sets buttons to loading while submitting, and then re-enables them `, async () => {
79
+ const initialStep = {
80
+ id: 'initial-step',
81
+ title: 'Initial step',
82
+ layout: [{ type: 'button', title: 'Submit', action: { id: 'action-id', url: '/submit' } }],
83
+ schemas: [],
84
+ model: { name: 'Name', number: '12345' },
85
+ };
86
+ const customFetch = vi.fn().mockImplementation(async () => {
87
+ await wait(100);
88
+ return new Response(JSON.stringify({
89
+ id: 'next-step',
90
+ title: 'Next step',
91
+ layout: [
92
+ { type: 'button', title: 'Finish', action: { id: 'action-id', url: '/submit' } },
93
+ ],
94
+ schemas: [],
95
+ }));
96
+ });
97
+ const props = {
98
+ flowId: 'test',
99
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
100
+ initialStep: initialStep,
101
+ customFetch,
102
+ onCompletion: vi.fn(),
103
+ onError: vi.fn(),
104
+ };
105
+ renderWithProviders(_jsx(DFComponent, Object.assign({}, props)));
106
+ const submitButton = screen.getByRole('button', { name: 'Submit' });
107
+ await user.click(submitButton);
108
+ expect(submitButton).toHaveAttribute('aria-disabled', 'true');
109
+ await waitFor(() => {
110
+ const submitButton2 = screen.getByRole('button', { name: 'Finish' });
111
+ expect(submitButton2).toBeEnabled();
112
+ });
113
+ });
114
+ it(`${name} - re-enables button if validation fails`, async () => {
115
+ const initialStep = {
116
+ id: 'initial-step',
117
+ title: 'Initial step',
118
+ layout: [
119
+ { type: 'form', schemaId: '#schema' },
120
+ { type: 'button', title: 'Submit', action: { id: 'action-id', url: '/submit' } },
121
+ ],
122
+ schemas: [{ type: 'string', $id: '#schema', title: 'Name' }],
123
+ };
124
+ const customFetch = vi.fn();
125
+ const props = {
126
+ flowId: 'test',
127
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
128
+ initialStep: initialStep,
129
+ customFetch,
130
+ onCompletion: vi.fn(),
131
+ onError: vi.fn(),
132
+ };
133
+ renderWithProviders(_jsx(DFComponent, Object.assign({}, props)));
134
+ const submitButton = screen.getByText('Submit');
135
+ await user.click(submitButton);
136
+ await waitFor(() => {
137
+ expect(submitButton).toBeEnabled();
138
+ expect(submitButton).not.toHaveAttribute('aria-busy', 'true');
139
+ });
140
+ });
141
+ it(`${name} - only shows loader on the button that was clicked`, async () => {
142
+ const initialStep = {
143
+ id: 'initial-step',
144
+ title: 'Initial step',
145
+ layout: [
146
+ { type: 'button', title: 'Submit', action: { id: 'action-id', url: '/submit' } },
147
+ { type: 'button', title: 'Continue', action: { id: 'action-id', url: '/submit' } },
148
+ ],
149
+ schemas: [],
150
+ model: { name: 'Name', number: '12345' },
151
+ };
152
+ const customFetch = vi.fn().mockImplementation(async () => {
153
+ return new Response(JSON.stringify({
154
+ id: 'next-step',
155
+ title: 'Next step',
156
+ layout: [
157
+ { type: 'button', title: 'Finish', action: { id: 'action-id', url: '/submit' } },
158
+ ],
159
+ schemas: [],
160
+ }));
161
+ });
162
+ const props = {
163
+ flowId: 'test',
164
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
165
+ initialStep: initialStep,
166
+ customFetch,
167
+ onCompletion: vi.fn(),
168
+ onError: vi.fn(),
169
+ };
170
+ renderWithProviders(_jsx(DFComponent, Object.assign({}, props)));
171
+ const submitButton = screen.getByRole('button', { name: 'Submit' });
172
+ const continueButton = screen.getByRole('button', { name: 'Continue' });
173
+ await user.click(submitButton);
174
+ expect(submitButton).toHaveAttribute('aria-disabled', 'true');
175
+ expect(continueButton).not.toHaveAttribute('aria-disabled', 'true');
176
+ });
177
+ });
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { screen } from '@testing-library/react';
3
+ import { vi } from 'vitest';
4
+ import { DynamicFlow } from '../dynamicFlow/DynamicFlow';
5
+ import { renderWithProviders } from '../test-utils/rtl-utils';
6
+ const props = {
7
+ flowId: 'test',
8
+ initialStep: {
9
+ id: 'step-id',
10
+ title: 'Step Title',
11
+ description: 'Step description.',
12
+ schemas: [],
13
+ layout: [],
14
+ },
15
+ onCompletion: vi.fn(),
16
+ onError: vi.fn(),
17
+ customFetch: vi.fn(),
18
+ };
19
+ describe('When the "displayStepTitle" flag is set to "false"', () => {
20
+ it('hides the step title in the StepRenderer', () => {
21
+ renderWithProviders(_jsx(DynamicFlow, Object.assign({}, props, { displayStepTitle: false })));
22
+ expect(screen.queryByText('Step description.')).toBeInTheDocument();
23
+ expect(screen.queryByText('Step Title')).not.toBeInTheDocument();
24
+ });
25
+ });
26
+ describe('When the "displayStepTitle" flag is set to "true"', () => {
27
+ it('shows the step title in the StepRenderer', () => {
28
+ renderWithProviders(_jsx(DynamicFlow, Object.assign({}, props, { displayStepTitle: true })));
29
+ expect(screen.queryByText('Step description.')).toBeInTheDocument();
30
+ expect(screen.queryByText('Step Title')).toBeInTheDocument();
31
+ });
32
+ });
33
+ describe('When the "hideStepTitle" feature is provided', () => {
34
+ it('hides the step title in the StepRenderer', () => {
35
+ renderWithProviders(_jsx(DynamicFlow, Object.assign({}, props, { features: { hideStepTitle: true } })));
36
+ expect(screen.queryByText('Step description.')).toBeInTheDocument();
37
+ expect(screen.queryByText('Step Title')).not.toBeInTheDocument();
38
+ });
39
+ });
40
+ describe('When the "hideStepTitle" feature is provided as "false"', () => {
41
+ it('shows the step title in the StepRenderer', () => {
42
+ renderWithProviders(_jsx(DynamicFlow, Object.assign({}, props, { features: { hideStepTitle: false } })));
43
+ expect(screen.queryByText('Step description.')).toBeInTheDocument();
44
+ expect(screen.queryByText('Step Title')).toBeInTheDocument();
45
+ });
46
+ });
47
+ describe('When both "displayStepTitle" flag and "hideStepTitle" feature are provided', () => {
48
+ it('gives precedence to the "hideStepTitle" feature to hide the step title', () => {
49
+ renderWithProviders(_jsx(DynamicFlow, Object.assign({}, props, { displayStepTitle: true, features: { hideStepTitle: true } })));
50
+ expect(screen.queryByText('Step description.')).toBeInTheDocument();
51
+ expect(screen.queryByText('Step Title')).not.toBeInTheDocument();
52
+ });
53
+ it('gives precedence to the "hideStepTitle" feature (even when false) to show the step title', () => {
54
+ renderWithProviders(_jsx(DynamicFlow, Object.assign({}, props, { displayStepTitle: true, features: { hideStepTitle: false } })));
55
+ expect(screen.queryByText('Step description.')).toBeInTheDocument();
56
+ expect(screen.queryByText('Step Title')).toBeInTheDocument();
57
+ });
58
+ });