@wise/dynamic-flow-client 5.11.0 → 5.12.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/build/controller/executePoll.js +17 -2
- package/build/main.js +15 -1
- package/build/main.mjs +15 -1
- package/build/tests/ListLayout.test.js +23 -0
- package/build/tests/Polling.test.js +87 -2
- package/build/tsconfig.types.tsbuildinfo +1 -0
- package/build/types/controller/executePoll.d.ts.map +1 -1
- package/package.json +20 -21
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { normaliseBehavior } from '../domain/mappers/utils/behavior-utils';
|
|
1
2
|
import { getResponseTypeFromHeader } from './getResponseType';
|
|
2
|
-
import { assertActionResponseBody, assertStepResponseBody, isActionResponseBody, parseResponseBodyAsJsonElement, } from './response-utils';
|
|
3
|
+
import { assertActionResponseBody, assertModalResponseBody, assertStepResponseBody, assertSubflowResponseBody, isActionResponseBody, parseResponseBodyAsJsonElement, } from './response-utils';
|
|
3
4
|
export const executePoll = async (props) => {
|
|
4
5
|
const { errorBehavior, signal, url, httpClient, trackEvent } = props;
|
|
5
6
|
try {
|
|
@@ -27,7 +28,21 @@ export const executePoll = async (props) => {
|
|
|
27
28
|
assertActionResponseBody(body);
|
|
28
29
|
return { type: 'behavior', behavior: actionResponseBodyToBehavior(body) };
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
case 'modal': {
|
|
32
|
+
assertModalResponseBody(body);
|
|
33
|
+
return { type: 'behavior', behavior: Object.assign({ type: 'modal' }, body) };
|
|
34
|
+
}
|
|
35
|
+
case 'no-op': {
|
|
36
|
+
return { type: 'behavior', behavior: { type: 'none' } };
|
|
37
|
+
}
|
|
38
|
+
case 'subflow': {
|
|
39
|
+
assertSubflowResponseBody(body);
|
|
40
|
+
return {
|
|
41
|
+
type: 'behavior',
|
|
42
|
+
behavior: normaliseBehavior(Object.assign({ type: 'subflow' }, body), []),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
case undefined: {
|
|
31
46
|
if (isActionResponseBody(body)) {
|
|
32
47
|
return { type: 'behavior', behavior: actionResponseBodyToBehavior(body) };
|
|
33
48
|
}
|
package/build/main.js
CHANGED
|
@@ -6474,7 +6474,21 @@ var executePoll = async (props) => {
|
|
|
6474
6474
|
assertActionResponseBody(body);
|
|
6475
6475
|
return { type: "behavior", behavior: actionResponseBodyToBehavior(body) };
|
|
6476
6476
|
}
|
|
6477
|
-
|
|
6477
|
+
case "modal": {
|
|
6478
|
+
assertModalResponseBody(body);
|
|
6479
|
+
return { type: "behavior", behavior: __spreadValues({ type: "modal" }, body) };
|
|
6480
|
+
}
|
|
6481
|
+
case "no-op": {
|
|
6482
|
+
return { type: "behavior", behavior: { type: "none" } };
|
|
6483
|
+
}
|
|
6484
|
+
case "subflow": {
|
|
6485
|
+
assertSubflowResponseBody(body);
|
|
6486
|
+
return {
|
|
6487
|
+
type: "behavior",
|
|
6488
|
+
behavior: normaliseBehavior(__spreadValues({ type: "subflow" }, body), [])
|
|
6489
|
+
};
|
|
6490
|
+
}
|
|
6491
|
+
case void 0: {
|
|
6478
6492
|
if (isActionResponseBody(body)) {
|
|
6479
6493
|
return { type: "behavior", behavior: actionResponseBodyToBehavior(body) };
|
|
6480
6494
|
}
|
package/build/main.mjs
CHANGED
|
@@ -6444,7 +6444,21 @@ var executePoll = async (props) => {
|
|
|
6444
6444
|
assertActionResponseBody(body);
|
|
6445
6445
|
return { type: "behavior", behavior: actionResponseBodyToBehavior(body) };
|
|
6446
6446
|
}
|
|
6447
|
-
|
|
6447
|
+
case "modal": {
|
|
6448
|
+
assertModalResponseBody(body);
|
|
6449
|
+
return { type: "behavior", behavior: __spreadValues({ type: "modal" }, body) };
|
|
6450
|
+
}
|
|
6451
|
+
case "no-op": {
|
|
6452
|
+
return { type: "behavior", behavior: { type: "none" } };
|
|
6453
|
+
}
|
|
6454
|
+
case "subflow": {
|
|
6455
|
+
assertSubflowResponseBody(body);
|
|
6456
|
+
return {
|
|
6457
|
+
type: "behavior",
|
|
6458
|
+
behavior: normaliseBehavior(__spreadValues({ type: "subflow" }, body), [])
|
|
6459
|
+
};
|
|
6460
|
+
}
|
|
6461
|
+
case void 0: {
|
|
6448
6462
|
if (isActionResponseBody(body)) {
|
|
6449
6463
|
return { type: "behavior", behavior: actionResponseBodyToBehavior(body) };
|
|
6450
6464
|
}
|
|
@@ -53,6 +53,20 @@ describe('ListLayout component', () => {
|
|
|
53
53
|
accessibilityDescription: 'Accessibility description for additional info 2',
|
|
54
54
|
},
|
|
55
55
|
},
|
|
56
|
+
{
|
|
57
|
+
analyticsId: 'item-3-aid',
|
|
58
|
+
title: 'Item 3',
|
|
59
|
+
tag: 'tag3',
|
|
60
|
+
supportingValues: { value: 'value3', subvalue: 'subvalue3' },
|
|
61
|
+
callToAction: {
|
|
62
|
+
title: 'Go to FooBar',
|
|
63
|
+
behavior: {
|
|
64
|
+
type: 'link',
|
|
65
|
+
url: 'https://foo.bar',
|
|
66
|
+
},
|
|
67
|
+
accessibilityDescription: 'Accessibility description for item 3 link',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
56
70
|
],
|
|
57
71
|
},
|
|
58
72
|
],
|
|
@@ -66,6 +80,15 @@ describe('ListLayout component', () => {
|
|
|
66
80
|
await user.click(screen.getByRole('button', { name: 'Click me' }));
|
|
67
81
|
expect(props.httpClient).toHaveBeenCalledWith('/item-cta', expect.anything());
|
|
68
82
|
});
|
|
83
|
+
it('should render a link for the item with the correct href and accessibility description', () => {
|
|
84
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({}, props)));
|
|
85
|
+
const link = screen.getByRole('link', { name: 'Go to FooBar' });
|
|
86
|
+
expect(link).toBeInTheDocument();
|
|
87
|
+
expect(link).toHaveAttribute('href', 'https://foo.bar');
|
|
88
|
+
expect(link).toHaveAttribute('target', '_blank');
|
|
89
|
+
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
|
|
90
|
+
expect(link).toHaveAttribute('aria-description', 'Accessibility description for item 3 link');
|
|
91
|
+
});
|
|
69
92
|
});
|
|
70
93
|
describe('Extra props', () => {
|
|
71
94
|
const props = Object.assign(Object.assign({}, getDefaultProps()), { initialStep: getInitialStep() });
|
|
@@ -8,7 +8,7 @@ import { abortableDelay, respondWith, respondWithDelay } from '../test-utils/fet
|
|
|
8
8
|
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
|
|
9
9
|
const cases = [
|
|
10
10
|
[
|
|
11
|
-
'a config with onError.behavior of type "action" (spec
|
|
11
|
+
'a config with onError.behavior of type "action" (current behavior spec)',
|
|
12
12
|
{
|
|
13
13
|
url: '/polling',
|
|
14
14
|
interval: 2,
|
|
@@ -35,7 +35,7 @@ const cases = [
|
|
|
35
35
|
},
|
|
36
36
|
],
|
|
37
37
|
[
|
|
38
|
-
'a config with onError.action',
|
|
38
|
+
'a config with onError.action (deprecated polling spec)',
|
|
39
39
|
{
|
|
40
40
|
url: '/polling',
|
|
41
41
|
interval: 2,
|
|
@@ -614,4 +614,89 @@ describe.each(cases)('Given %s', (_, pollingConfig) => {
|
|
|
614
614
|
expect(mockHttpClient).toHaveBeenCalledTimes(1);
|
|
615
615
|
});
|
|
616
616
|
});
|
|
617
|
+
describe('when the response is a modal', () => {
|
|
618
|
+
it('renders the modal content and stops polling', async () => {
|
|
619
|
+
const mockHttpClient = vi.fn().mockImplementation(async (input) => {
|
|
620
|
+
switch (input) {
|
|
621
|
+
case '/polling':
|
|
622
|
+
return respondWith({ title: 'Modal Title', content: [{ type: 'paragraph', text: 'Modal content' }] }, { headers: { 'X-DF-Response-Type': 'modal' } });
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
renderWithProviders(_jsx(DynamicFlowWise, { flowId: "polling-flow", httpClient: mockHttpClient, initialStep: initialStep(), onError: () => { }, onCompletion: () => { } }));
|
|
626
|
+
await waitFor(() => {
|
|
627
|
+
expect(screen.getByText('polling title')).toBeInTheDocument();
|
|
628
|
+
});
|
|
629
|
+
expect(mockHttpClient).toHaveBeenCalledTimes(1);
|
|
630
|
+
expect(mockHttpClient).toHaveBeenCalledWith('/polling', expect.anything());
|
|
631
|
+
await waitFor(() => {
|
|
632
|
+
expect(screen.getByText('Modal Title')).toBeInTheDocument();
|
|
633
|
+
});
|
|
634
|
+
expect(screen.getByText('Modal content')).toBeInTheDocument();
|
|
635
|
+
// original step remains visible behind the modal
|
|
636
|
+
expect(screen.getByText('polling title')).toBeInTheDocument();
|
|
637
|
+
// polling stops after the modal is shown
|
|
638
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
639
|
+
expect(mockHttpClient).toHaveBeenCalledTimes(1);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
describe('when the response is a no-op', () => {
|
|
643
|
+
it('stops polling without changing the UI', async () => {
|
|
644
|
+
const mockHttpClient = vi.fn().mockImplementation(async (input) => {
|
|
645
|
+
switch (input) {
|
|
646
|
+
case '/polling':
|
|
647
|
+
return respondWith(null, { headers: { 'X-DF-Response-Type': 'no-op' } });
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
const onError = vi.fn();
|
|
651
|
+
renderWithProviders(_jsx(DynamicFlowWise, { flowId: "polling-flow", httpClient: mockHttpClient, initialStep: initialStep(), onError: onError, onCompletion: () => { } }));
|
|
652
|
+
await waitFor(() => {
|
|
653
|
+
expect(screen.getByText('polling title')).toBeInTheDocument();
|
|
654
|
+
});
|
|
655
|
+
expect(mockHttpClient).toHaveBeenCalledTimes(1);
|
|
656
|
+
expect(mockHttpClient).toHaveBeenCalledWith('/polling', expect.anything());
|
|
657
|
+
// polling stops, but nothing changes in the UI
|
|
658
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
659
|
+
expect(mockHttpClient).toHaveBeenCalledTimes(1);
|
|
660
|
+
expect(screen.getByText('polling title')).toBeInTheDocument();
|
|
661
|
+
// the flow doesn't fail
|
|
662
|
+
expect(onError).not.toHaveBeenCalled();
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
describe('when the response is a subflow', () => {
|
|
666
|
+
it('launches the subflow modally and stops polling', async () => {
|
|
667
|
+
const mockHttpClient = vi.fn().mockImplementation(async (input) => {
|
|
668
|
+
switch (input) {
|
|
669
|
+
case '/polling':
|
|
670
|
+
return respondWith({
|
|
671
|
+
referrerId: 'polling-flow',
|
|
672
|
+
launchConfig: {
|
|
673
|
+
type: 'dynamic',
|
|
674
|
+
request: { url: '/subflow-start', method: 'GET' },
|
|
675
|
+
presentation: { type: 'modal' },
|
|
676
|
+
},
|
|
677
|
+
}, { headers: { 'X-DF-Response-Type': 'subflow' } });
|
|
678
|
+
case '/subflow-start':
|
|
679
|
+
return respondWith({
|
|
680
|
+
id: 'subflow-step',
|
|
681
|
+
title: 'Subflow Step',
|
|
682
|
+
schemas: [],
|
|
683
|
+
layout: [],
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
renderWithProviders(_jsx(DynamicFlowWise, { flowId: "polling-flow", httpClient: mockHttpClient, initialStep: initialStep(), onError: () => { }, onCompletion: () => { } }));
|
|
688
|
+
await waitFor(() => {
|
|
689
|
+
expect(screen.getByText('polling title')).toBeInTheDocument();
|
|
690
|
+
});
|
|
691
|
+
expect(mockHttpClient).toHaveBeenCalledWith('/polling', expect.anything());
|
|
692
|
+
await waitFor(() => {
|
|
693
|
+
expect(screen.getByText('Subflow Step')).toBeInTheDocument();
|
|
694
|
+
});
|
|
695
|
+
// original step still visible behind the subflow modal
|
|
696
|
+
expect(screen.getByText('polling title')).toBeInTheDocument();
|
|
697
|
+
// polling stops after the subflow is launched
|
|
698
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
699
|
+
expect(mockHttpClient).toHaveBeenCalledTimes(2); // one poll + one subflow start
|
|
700
|
+
});
|
|
701
|
+
});
|
|
617
702
|
});
|