@wise/dynamic-flow-client 5.9.2 → 5.10.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.
- package/build/controller/FlowController.js +5 -1
- package/build/controller/executeRefresh.js +8 -2
- package/build/controller/executeRequest.js +3 -0
- package/build/controller/executeSubmission.js +1 -0
- package/build/controller/getResponseType.js +3 -3
- package/build/main.js +18 -5
- package/build/main.mjs +18 -5
- package/build/stories/spec/behavior/Copy.story.js +14 -2
- package/build/stories/spec/behavior/Link.story.js +40 -0
- package/build/stories/spec/behavior/Modal.story.js +4 -1
- package/build/stories/spec/layouts/Upsell.story.js +1 -1
- package/build/tests/NoOp.test.js +194 -0
- package/build/tests/Submission.test.js +163 -18
- package/build/tests/Upsell.test.js +34 -6
- package/build/types/controller/FlowController.d.ts.map +1 -1
- package/build/types/controller/executeRefresh.d.ts +1 -1
- package/build/types/controller/executeRefresh.d.ts.map +1 -1
- package/build/types/controller/executeRequest.d.ts +2 -0
- package/build/types/controller/executeRequest.d.ts.map +1 -1
- package/build/types/controller/executeSubmission.d.ts.map +1 -1
- package/build/types/controller/getResponseType.d.ts +2 -2
- package/build/types/controller/getResponseType.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -273,6 +273,10 @@ export const createFlowController = (props) => {
|
|
|
273
273
|
rootComponent.setLoadingState('idle');
|
|
274
274
|
break;
|
|
275
275
|
}
|
|
276
|
+
case 'no-op': {
|
|
277
|
+
rootComponent.setLoadingState('idle');
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
276
280
|
}
|
|
277
281
|
}
|
|
278
282
|
catch (error) {
|
|
@@ -301,7 +305,7 @@ export const createFlowController = (props) => {
|
|
|
301
305
|
updateStep(Object.assign(Object.assign({}, command.step), { errors }), command.etag);
|
|
302
306
|
}
|
|
303
307
|
break;
|
|
304
|
-
case '
|
|
308
|
+
case 'no-op':
|
|
305
309
|
rootComponent.setLoadingState('idle');
|
|
306
310
|
break;
|
|
307
311
|
case 'error': {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getErrorMessage } from './getErrorMessage';
|
|
2
2
|
import { assertStepResponseBody, isErrorResponseBody, parseResponseBodyAsJsonElement, } from './response-utils';
|
|
3
|
+
import { getResponseType } from './getResponseType';
|
|
3
4
|
export const executeRefresh = async (props) => {
|
|
4
5
|
const { abortSignal, url, model, etag, analytics, httpClient, trackEvent, logEvent } = props;
|
|
5
6
|
trackEvent('Refresh Triggered', analytics);
|
|
@@ -12,7 +13,7 @@ export const executeRefresh = async (props) => {
|
|
|
12
13
|
});
|
|
13
14
|
if (response.status === 304) {
|
|
14
15
|
trackEvent('Refresh Succeeded', analytics);
|
|
15
|
-
return { type: '
|
|
16
|
+
return { type: 'no-op' };
|
|
16
17
|
}
|
|
17
18
|
if (!response.ok) {
|
|
18
19
|
const responseBody = await parseResponseBodyAsJsonElement(response).catch(() => ({}));
|
|
@@ -23,6 +24,11 @@ export const executeRefresh = async (props) => {
|
|
|
23
24
|
}
|
|
24
25
|
const newEtag = response.headers.get('etag') || null;
|
|
25
26
|
const body = await parseResponseBodyAsJsonElement(response);
|
|
27
|
+
const responseType = getResponseType(response.headers, body);
|
|
28
|
+
if (responseType === 'no-op') {
|
|
29
|
+
trackEvent('Refresh Succeeded', analytics);
|
|
30
|
+
return { type: 'no-op' };
|
|
31
|
+
}
|
|
26
32
|
assertStepResponseBody(body);
|
|
27
33
|
trackEvent('Refresh Succeeded', analytics);
|
|
28
34
|
return { type: 'refresh-step', step: body, etag: newEtag };
|
|
@@ -30,7 +36,7 @@ export const executeRefresh = async (props) => {
|
|
|
30
36
|
catch (error) {
|
|
31
37
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
32
38
|
trackEvent('Refresh Aborted', analytics);
|
|
33
|
-
return { type: '
|
|
39
|
+
return { type: 'no-op' };
|
|
34
40
|
}
|
|
35
41
|
trackEvent('Refresh Failed', analytics);
|
|
36
42
|
logEvent('error', 'Dynamic Flow - Refresh Failed', Object.assign(Object.assign({}, analytics), { errorMessage: getErrorMessage(error) }));
|
|
@@ -60,6 +60,9 @@ export const executeRequest = async (props) => {
|
|
|
60
60
|
assertModalResponseBody(responseBody);
|
|
61
61
|
return { type: 'behavior', behavior: Object.assign(Object.assign({}, responseBody), { type: 'modal' }) };
|
|
62
62
|
}
|
|
63
|
+
case 'no-op': {
|
|
64
|
+
return { type: 'no-op' };
|
|
65
|
+
}
|
|
63
66
|
default: {
|
|
64
67
|
throw new Error(`Unsupported response type: ${String(responseType)}`);
|
|
65
68
|
}
|
|
@@ -48,6 +48,7 @@ export const executeSubmission = async (props) => {
|
|
|
48
48
|
trackSubmissionEvent('Action Succeeded', { actionId });
|
|
49
49
|
return Object.assign(Object.assign({}, command), { result: recursiveMerge(command.result, result) });
|
|
50
50
|
}
|
|
51
|
+
case 'no-op':
|
|
51
52
|
default: {
|
|
52
53
|
trackSubmissionEvent('Action Succeeded', { actionId });
|
|
53
54
|
return command;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isObject } from '../utils/type-validators';
|
|
2
|
-
const responseTypes = ['step', 'action', 'exit', 'modal', 'subflow'];
|
|
2
|
+
const responseTypes = ['step', 'action', 'exit', 'modal', 'subflow', 'no-op'];
|
|
3
3
|
/**
|
|
4
|
-
* Returns either 'step', 'action', or '
|
|
4
|
+
* Returns either 'step', 'action', 'exit', 'modal', 'subflow', or 'no-op' based on the response headers and body.
|
|
5
5
|
* This function takes a response body parameter because, for legacy reasons, we still need to consider the body
|
|
6
6
|
* to determine the response type, in cases where the response headers are not set.
|
|
7
7
|
* Ideally it should just be a matter of checking the "X-Df-Response-Type" response header.
|
|
@@ -30,6 +30,6 @@ export const getResponseTypeFromHeader = (headers) => {
|
|
|
30
30
|
};
|
|
31
31
|
function assertDFResponseType(type) {
|
|
32
32
|
if (!responseTypes.includes(type)) {
|
|
33
|
-
throw new Error("Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', '
|
|
33
|
+
throw new Error("Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'modal', 'subflow', 'no-op'.");
|
|
34
34
|
}
|
|
35
35
|
}
|
package/build/main.js
CHANGED
|
@@ -6006,7 +6006,7 @@ var handleErrorResponse = async (response, actionId, trackEvent) => {
|
|
|
6006
6006
|
};
|
|
6007
6007
|
|
|
6008
6008
|
// src/controller/getResponseType.ts
|
|
6009
|
-
var responseTypes = ["step", "action", "exit", "modal", "subflow"];
|
|
6009
|
+
var responseTypes = ["step", "action", "exit", "modal", "subflow", "no-op"];
|
|
6010
6010
|
var getResponseType = (headers, body) => {
|
|
6011
6011
|
const headerResponseType = getResponseTypeFromHeader(headers);
|
|
6012
6012
|
if (headerResponseType) {
|
|
@@ -6031,7 +6031,7 @@ var getResponseTypeFromHeader = (headers) => {
|
|
|
6031
6031
|
function assertDFResponseType(type) {
|
|
6032
6032
|
if (!responseTypes.includes(type)) {
|
|
6033
6033
|
throw new Error(
|
|
6034
|
-
"Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', '
|
|
6034
|
+
"Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'modal', 'subflow', 'no-op'."
|
|
6035
6035
|
);
|
|
6036
6036
|
}
|
|
6037
6037
|
}
|
|
@@ -6109,6 +6109,9 @@ var executeRequest = async (props) => {
|
|
|
6109
6109
|
assertModalResponseBody(responseBody);
|
|
6110
6110
|
return { type: "behavior", behavior: __spreadProps(__spreadValues({}, responseBody), { type: "modal" }) };
|
|
6111
6111
|
}
|
|
6112
|
+
case "no-op": {
|
|
6113
|
+
return { type: "no-op" };
|
|
6114
|
+
}
|
|
6112
6115
|
default: {
|
|
6113
6116
|
throw new Error(`Unsupported response type: ${String(responseType)}`);
|
|
6114
6117
|
}
|
|
@@ -6177,6 +6180,7 @@ var executeSubmission = async (props) => {
|
|
|
6177
6180
|
trackSubmissionEvent("Action Succeeded", { actionId });
|
|
6178
6181
|
return __spreadProps(__spreadValues({}, command), { result: recursiveMerge(command.result, result) });
|
|
6179
6182
|
}
|
|
6183
|
+
case "no-op":
|
|
6180
6184
|
default: {
|
|
6181
6185
|
trackSubmissionEvent("Action Succeeded", { actionId });
|
|
6182
6186
|
return command;
|
|
@@ -6503,7 +6507,7 @@ var executeRefresh = async (props) => {
|
|
|
6503
6507
|
});
|
|
6504
6508
|
if (response.status === 304) {
|
|
6505
6509
|
trackEvent("Refresh Succeeded", analytics);
|
|
6506
|
-
return { type: "
|
|
6510
|
+
return { type: "no-op" };
|
|
6507
6511
|
}
|
|
6508
6512
|
if (!response.ok) {
|
|
6509
6513
|
const responseBody = await parseResponseBodyAsJsonElement(response).catch(() => ({}));
|
|
@@ -6518,13 +6522,18 @@ var executeRefresh = async (props) => {
|
|
|
6518
6522
|
}
|
|
6519
6523
|
const newEtag = response.headers.get("etag") || null;
|
|
6520
6524
|
const body = await parseResponseBodyAsJsonElement(response);
|
|
6525
|
+
const responseType = getResponseType(response.headers, body);
|
|
6526
|
+
if (responseType === "no-op") {
|
|
6527
|
+
trackEvent("Refresh Succeeded", analytics);
|
|
6528
|
+
return { type: "no-op" };
|
|
6529
|
+
}
|
|
6521
6530
|
assertStepResponseBody(body);
|
|
6522
6531
|
trackEvent("Refresh Succeeded", analytics);
|
|
6523
6532
|
return { type: "refresh-step", step: body, etag: newEtag };
|
|
6524
6533
|
} catch (error) {
|
|
6525
6534
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
6526
6535
|
trackEvent("Refresh Aborted", analytics);
|
|
6527
|
-
return { type: "
|
|
6536
|
+
return { type: "no-op" };
|
|
6528
6537
|
}
|
|
6529
6538
|
trackEvent("Refresh Failed", analytics);
|
|
6530
6539
|
logEvent("error", "Dynamic Flow - Refresh Failed", __spreadProps(__spreadValues({}, analytics), {
|
|
@@ -6889,6 +6898,10 @@ var createFlowController = (props) => {
|
|
|
6889
6898
|
rootComponent.setLoadingState("idle");
|
|
6890
6899
|
break;
|
|
6891
6900
|
}
|
|
6901
|
+
case "no-op": {
|
|
6902
|
+
rootComponent.setLoadingState("idle");
|
|
6903
|
+
break;
|
|
6904
|
+
}
|
|
6892
6905
|
}
|
|
6893
6906
|
} catch (error) {
|
|
6894
6907
|
closeWithError(error);
|
|
@@ -6920,7 +6933,7 @@ var createFlowController = (props) => {
|
|
|
6920
6933
|
updateStep(__spreadProps(__spreadValues({}, command.step), { errors }), command.etag);
|
|
6921
6934
|
}
|
|
6922
6935
|
break;
|
|
6923
|
-
case "
|
|
6936
|
+
case "no-op":
|
|
6924
6937
|
rootComponent.setLoadingState("idle");
|
|
6925
6938
|
break;
|
|
6926
6939
|
case "error": {
|
package/build/main.mjs
CHANGED
|
@@ -5976,7 +5976,7 @@ var handleErrorResponse = async (response, actionId, trackEvent) => {
|
|
|
5976
5976
|
};
|
|
5977
5977
|
|
|
5978
5978
|
// src/controller/getResponseType.ts
|
|
5979
|
-
var responseTypes = ["step", "action", "exit", "modal", "subflow"];
|
|
5979
|
+
var responseTypes = ["step", "action", "exit", "modal", "subflow", "no-op"];
|
|
5980
5980
|
var getResponseType = (headers, body) => {
|
|
5981
5981
|
const headerResponseType = getResponseTypeFromHeader(headers);
|
|
5982
5982
|
if (headerResponseType) {
|
|
@@ -6001,7 +6001,7 @@ var getResponseTypeFromHeader = (headers) => {
|
|
|
6001
6001
|
function assertDFResponseType(type) {
|
|
6002
6002
|
if (!responseTypes.includes(type)) {
|
|
6003
6003
|
throw new Error(
|
|
6004
|
-
"Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', '
|
|
6004
|
+
"Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'modal', 'subflow', 'no-op'."
|
|
6005
6005
|
);
|
|
6006
6006
|
}
|
|
6007
6007
|
}
|
|
@@ -6079,6 +6079,9 @@ var executeRequest = async (props) => {
|
|
|
6079
6079
|
assertModalResponseBody(responseBody);
|
|
6080
6080
|
return { type: "behavior", behavior: __spreadProps(__spreadValues({}, responseBody), { type: "modal" }) };
|
|
6081
6081
|
}
|
|
6082
|
+
case "no-op": {
|
|
6083
|
+
return { type: "no-op" };
|
|
6084
|
+
}
|
|
6082
6085
|
default: {
|
|
6083
6086
|
throw new Error(`Unsupported response type: ${String(responseType)}`);
|
|
6084
6087
|
}
|
|
@@ -6147,6 +6150,7 @@ var executeSubmission = async (props) => {
|
|
|
6147
6150
|
trackSubmissionEvent("Action Succeeded", { actionId });
|
|
6148
6151
|
return __spreadProps(__spreadValues({}, command), { result: recursiveMerge(command.result, result) });
|
|
6149
6152
|
}
|
|
6153
|
+
case "no-op":
|
|
6150
6154
|
default: {
|
|
6151
6155
|
trackSubmissionEvent("Action Succeeded", { actionId });
|
|
6152
6156
|
return command;
|
|
@@ -6473,7 +6477,7 @@ var executeRefresh = async (props) => {
|
|
|
6473
6477
|
});
|
|
6474
6478
|
if (response.status === 304) {
|
|
6475
6479
|
trackEvent("Refresh Succeeded", analytics);
|
|
6476
|
-
return { type: "
|
|
6480
|
+
return { type: "no-op" };
|
|
6477
6481
|
}
|
|
6478
6482
|
if (!response.ok) {
|
|
6479
6483
|
const responseBody = await parseResponseBodyAsJsonElement(response).catch(() => ({}));
|
|
@@ -6488,13 +6492,18 @@ var executeRefresh = async (props) => {
|
|
|
6488
6492
|
}
|
|
6489
6493
|
const newEtag = response.headers.get("etag") || null;
|
|
6490
6494
|
const body = await parseResponseBodyAsJsonElement(response);
|
|
6495
|
+
const responseType = getResponseType(response.headers, body);
|
|
6496
|
+
if (responseType === "no-op") {
|
|
6497
|
+
trackEvent("Refresh Succeeded", analytics);
|
|
6498
|
+
return { type: "no-op" };
|
|
6499
|
+
}
|
|
6491
6500
|
assertStepResponseBody(body);
|
|
6492
6501
|
trackEvent("Refresh Succeeded", analytics);
|
|
6493
6502
|
return { type: "refresh-step", step: body, etag: newEtag };
|
|
6494
6503
|
} catch (error) {
|
|
6495
6504
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
6496
6505
|
trackEvent("Refresh Aborted", analytics);
|
|
6497
|
-
return { type: "
|
|
6506
|
+
return { type: "no-op" };
|
|
6498
6507
|
}
|
|
6499
6508
|
trackEvent("Refresh Failed", analytics);
|
|
6500
6509
|
logEvent("error", "Dynamic Flow - Refresh Failed", __spreadProps(__spreadValues({}, analytics), {
|
|
@@ -6859,6 +6868,10 @@ var createFlowController = (props) => {
|
|
|
6859
6868
|
rootComponent.setLoadingState("idle");
|
|
6860
6869
|
break;
|
|
6861
6870
|
}
|
|
6871
|
+
case "no-op": {
|
|
6872
|
+
rootComponent.setLoadingState("idle");
|
|
6873
|
+
break;
|
|
6874
|
+
}
|
|
6862
6875
|
}
|
|
6863
6876
|
} catch (error) {
|
|
6864
6877
|
closeWithError(error);
|
|
@@ -6890,7 +6903,7 @@ var createFlowController = (props) => {
|
|
|
6890
6903
|
updateStep(__spreadProps(__spreadValues({}, command.step), { errors }), command.etag);
|
|
6891
6904
|
}
|
|
6892
6905
|
break;
|
|
6893
|
-
case "
|
|
6906
|
+
case "no-op":
|
|
6894
6907
|
rootComponent.setLoadingState("idle");
|
|
6895
6908
|
break;
|
|
6896
6909
|
case "error": {
|
|
@@ -7,8 +7,11 @@ export default {
|
|
|
7
7
|
chromatic: { disableSnapshot: true },
|
|
8
8
|
},
|
|
9
9
|
};
|
|
10
|
+
const httpClient = async () => new Promise((resolve) => {
|
|
11
|
+
setTimeout(resolve, 1000);
|
|
12
|
+
});
|
|
10
13
|
export function Copy() {
|
|
11
|
-
return renderWithStep(copyStep);
|
|
14
|
+
return renderWithStep(copyStep, httpClient);
|
|
12
15
|
}
|
|
13
16
|
const copyStep = {
|
|
14
17
|
id: 'copy',
|
|
@@ -49,11 +52,20 @@ const copyStep = {
|
|
|
49
52
|
{
|
|
50
53
|
type: 'button',
|
|
51
54
|
title: 'Click to copy',
|
|
52
|
-
control: '
|
|
55
|
+
control: 'secondary',
|
|
53
56
|
behavior: {
|
|
54
57
|
type: 'copy',
|
|
55
58
|
content: 'This is a button',
|
|
56
59
|
},
|
|
57
60
|
},
|
|
61
|
+
{
|
|
62
|
+
type: 'button',
|
|
63
|
+
title: 'Submit',
|
|
64
|
+
control: 'primary',
|
|
65
|
+
behavior: {
|
|
66
|
+
type: 'action',
|
|
67
|
+
action: { url: '/submit' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
58
70
|
],
|
|
59
71
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import DynamicFlowWise from '../../../test-utils/DynamicFlowWise';
|
|
2
|
+
import { renderWithStep } from '../../utils/render-utils';
|
|
3
|
+
export default {
|
|
4
|
+
component: DynamicFlowWise,
|
|
5
|
+
title: 'Spec/Behavior',
|
|
6
|
+
parameters: {
|
|
7
|
+
chromatic: { disableSnapshot: true },
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const httpClient = async () => new Promise((resolve) => {
|
|
11
|
+
setTimeout(resolve, 1000);
|
|
12
|
+
});
|
|
13
|
+
export function Link() {
|
|
14
|
+
return renderWithStep(step, httpClient);
|
|
15
|
+
}
|
|
16
|
+
const step = {
|
|
17
|
+
id: 'copy',
|
|
18
|
+
title: 'Link Behavior',
|
|
19
|
+
schemas: [],
|
|
20
|
+
layout: [
|
|
21
|
+
{
|
|
22
|
+
type: 'button',
|
|
23
|
+
title: 'Open Google',
|
|
24
|
+
control: 'secondary',
|
|
25
|
+
behavior: {
|
|
26
|
+
type: 'link',
|
|
27
|
+
url: 'https://www.google.com',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: 'button',
|
|
32
|
+
title: 'Submit',
|
|
33
|
+
control: 'primary',
|
|
34
|
+
behavior: {
|
|
35
|
+
type: 'action',
|
|
36
|
+
action: { url: '/submit' },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
@@ -7,8 +7,11 @@ export default {
|
|
|
7
7
|
chromatic: { disableSnapshot: true },
|
|
8
8
|
},
|
|
9
9
|
};
|
|
10
|
+
const httpClient = async () => new Promise((resolve) => {
|
|
11
|
+
setTimeout(resolve, 1000);
|
|
12
|
+
});
|
|
10
13
|
export function Modal() {
|
|
11
|
-
return renderWithStep(initialStep);
|
|
14
|
+
return renderWithStep(initialStep, httpClient);
|
|
12
15
|
}
|
|
13
16
|
const initialStep = {
|
|
14
17
|
id: 'modal',
|
|
@@ -33,7 +33,7 @@ const getStep = (urns, dismissable) => ({
|
|
|
33
33
|
},
|
|
34
34
|
callToAction: {
|
|
35
35
|
title: 'Do the thing!',
|
|
36
|
-
behavior: { type: '
|
|
36
|
+
behavior: { type: 'link', url: 'https://google.com' },
|
|
37
37
|
},
|
|
38
38
|
onDismiss: dismissable ? { url: '/upsell-dismissed', method: 'POST' } : undefined,
|
|
39
39
|
margin: 'lg',
|
|
@@ -0,0 +1,194 @@
|
|
|
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 { renderWithProviders, respondWith } from '../test-utils';
|
|
6
|
+
import DynamicFlowWise from '../test-utils/DynamicFlowWise';
|
|
7
|
+
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
|
|
8
|
+
const getProps = () => ({
|
|
9
|
+
flowId: 'flow-id',
|
|
10
|
+
onCompletion: vi.fn(),
|
|
11
|
+
onError: vi.fn(),
|
|
12
|
+
onEvent: vi.fn(),
|
|
13
|
+
onLog: vi.fn(),
|
|
14
|
+
});
|
|
15
|
+
const noOpHeaders = { 'X-Df-Response-Type': 'no-op' };
|
|
16
|
+
describe('No-op responses', () => {
|
|
17
|
+
describe('Action submissions', () => {
|
|
18
|
+
const buildInitialStep = () => ({
|
|
19
|
+
id: 'no-op-action-step',
|
|
20
|
+
title: 'No-Op Action Demo',
|
|
21
|
+
model: { message: '' },
|
|
22
|
+
schemas: [
|
|
23
|
+
{
|
|
24
|
+
$id: '#action-form',
|
|
25
|
+
type: 'object',
|
|
26
|
+
displayOrder: ['message'],
|
|
27
|
+
required: ['message'],
|
|
28
|
+
properties: {
|
|
29
|
+
message: {
|
|
30
|
+
title: 'Message',
|
|
31
|
+
type: 'string',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
layout: [
|
|
37
|
+
{ type: 'form', schemaId: '#action-form' },
|
|
38
|
+
{
|
|
39
|
+
type: 'button',
|
|
40
|
+
title: 'Submit',
|
|
41
|
+
action: { id: 'noop-action-id', url: '/action', method: 'POST' },
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'button',
|
|
45
|
+
title: 'Submit elsewhere',
|
|
46
|
+
action: { id: 'other-action-id', url: '/other-action', method: 'POST' },
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
it('should keep the current step when an action responds with no-op', async () => {
|
|
51
|
+
const initialStep = buildInitialStep();
|
|
52
|
+
const httpClient = vi.fn().mockResolvedValue(respondWith({}, { headers: noOpHeaders }));
|
|
53
|
+
const props = Object.assign(Object.assign({}, getProps()), { initialStep, httpClient });
|
|
54
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({}, props)));
|
|
55
|
+
await waitFor(() => {
|
|
56
|
+
expect(screen.getByText(initialStep.title)).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
const input = screen.getByLabelText('Message');
|
|
59
|
+
await user.type(input, 'first attempt');
|
|
60
|
+
await user.click(screen.getByText('Submit'));
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
expect(httpClient).toHaveBeenCalledTimes(1);
|
|
63
|
+
});
|
|
64
|
+
expect(httpClient).toHaveBeenCalledWith('/action', expect.objectContaining({ body: '{"message":"first attempt"}' }));
|
|
65
|
+
expect(screen.getByText(initialStep.title)).toBeInTheDocument();
|
|
66
|
+
expect(props.onError).not.toHaveBeenCalled();
|
|
67
|
+
expect(props.onEvent).toHaveBeenCalledWith('Dynamic Flow - Action Succeeded', expect.objectContaining({ actionId: 'noop-action-id' }));
|
|
68
|
+
});
|
|
69
|
+
it('should allow submitting another action after a no-op response', async () => {
|
|
70
|
+
const initialStep = buildInitialStep();
|
|
71
|
+
const nextStep = {
|
|
72
|
+
id: 'post-no-op-step',
|
|
73
|
+
title: 'Step after No-Op',
|
|
74
|
+
layout: [],
|
|
75
|
+
schemas: [],
|
|
76
|
+
};
|
|
77
|
+
const httpClient = vi
|
|
78
|
+
.fn()
|
|
79
|
+
.mockResolvedValueOnce(respondWith({}, { headers: noOpHeaders }))
|
|
80
|
+
.mockResolvedValueOnce(respondWith(nextStep));
|
|
81
|
+
const props = Object.assign(Object.assign({}, getProps()), { initialStep, httpClient });
|
|
82
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({}, props)));
|
|
83
|
+
await waitFor(() => {
|
|
84
|
+
expect(screen.getByText(initialStep.title)).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
const input = screen.getByLabelText('Message');
|
|
87
|
+
await user.type(input, 'first attempt');
|
|
88
|
+
await user.click(screen.getByText('Submit'));
|
|
89
|
+
await waitFor(() => {
|
|
90
|
+
expect(httpClient).toHaveBeenCalledTimes(1);
|
|
91
|
+
});
|
|
92
|
+
await user.clear(input);
|
|
93
|
+
await user.type(input, 'second attempt');
|
|
94
|
+
await user.click(screen.getByText('Submit elsewhere'));
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(screen.getByText('Step after No-Op')).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
expect(httpClient).toHaveBeenCalledTimes(2);
|
|
99
|
+
expect(httpClient).toHaveBeenCalledWith('/other-action', expect.objectContaining({ body: '{"message":"second attempt"}' }));
|
|
100
|
+
expect(props.onError).not.toHaveBeenCalled();
|
|
101
|
+
expect(props.onEvent).toHaveBeenCalledWith('Dynamic Flow - Action Succeeded', expect.objectContaining({ actionId: 'other-action-id' }));
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('Refresh requests', () => {
|
|
105
|
+
const buildInitialStep = () => ({
|
|
106
|
+
id: 'no-op-refresh-step',
|
|
107
|
+
title: 'No-Op Refresh Demo',
|
|
108
|
+
schemas: [
|
|
109
|
+
{
|
|
110
|
+
$id: '#refresh-form',
|
|
111
|
+
type: 'object',
|
|
112
|
+
displayOrder: ['color'],
|
|
113
|
+
properties: {
|
|
114
|
+
color: {
|
|
115
|
+
title: 'Color',
|
|
116
|
+
control: 'radio',
|
|
117
|
+
analyticsId: '#color-schema',
|
|
118
|
+
oneOf: [
|
|
119
|
+
{ const: 'red', title: 'Red' },
|
|
120
|
+
{ const: 'blue', title: 'Blue' },
|
|
121
|
+
],
|
|
122
|
+
onChange: { type: 'refresh' },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
layout: [
|
|
128
|
+
{ type: 'form', schemaId: '#refresh-form' },
|
|
129
|
+
{
|
|
130
|
+
type: 'button',
|
|
131
|
+
title: 'Submit',
|
|
132
|
+
action: { id: 'submit-id', url: '/submit', method: 'POST' },
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: 'button',
|
|
136
|
+
title: 'Submit step',
|
|
137
|
+
action: { id: 'step-submit-id', url: '/step-submit', method: 'POST' },
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
refreshUrl: '/refresh',
|
|
141
|
+
});
|
|
142
|
+
it('should treat a no-op refresh like a 304 response', async () => {
|
|
143
|
+
const initialStep = buildInitialStep();
|
|
144
|
+
const httpClient = vi.fn().mockResolvedValue(respondWith({}, { headers: noOpHeaders }));
|
|
145
|
+
const props = Object.assign(Object.assign({}, getProps()), { initialStep, httpClient });
|
|
146
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({}, props)));
|
|
147
|
+
await waitFor(() => {
|
|
148
|
+
expect(screen.getByText(initialStep.title)).toBeInTheDocument();
|
|
149
|
+
});
|
|
150
|
+
await user.click(screen.getByText('Red'));
|
|
151
|
+
await waitFor(() => {
|
|
152
|
+
expect(httpClient).toHaveBeenCalledTimes(1);
|
|
153
|
+
});
|
|
154
|
+
expect(screen.getByText(initialStep.title)).toBeInTheDocument();
|
|
155
|
+
expect(httpClient).toHaveBeenNthCalledWith(1, '/refresh', expect.objectContaining({ body: '{"color":"red"}' }));
|
|
156
|
+
expect(props.onError).not.toHaveBeenCalled();
|
|
157
|
+
expect(props.onEvent).toHaveBeenCalledWith('Dynamic Flow - Refresh Succeeded', expect.objectContaining({
|
|
158
|
+
flowId: 'flow-id',
|
|
159
|
+
stepId: initialStep.id,
|
|
160
|
+
schema: '#color-schema',
|
|
161
|
+
}));
|
|
162
|
+
});
|
|
163
|
+
it('should allow refreshing again and submitting after a no-op response', async () => {
|
|
164
|
+
const initialStep = buildInitialStep();
|
|
165
|
+
const refreshedStep = Object.assign(Object.assign({}, initialStep), { id: 'refreshed-step', title: 'Refreshed after No-Op', model: { color: 'blue' } });
|
|
166
|
+
const httpClient = vi
|
|
167
|
+
.fn()
|
|
168
|
+
.mockResolvedValueOnce(respondWith({}, { headers: noOpHeaders }))
|
|
169
|
+
.mockResolvedValueOnce(respondWith(refreshedStep))
|
|
170
|
+
.mockResolvedValueOnce(respondWith(refreshedStep));
|
|
171
|
+
const props = Object.assign(Object.assign({}, getProps()), { initialStep, httpClient });
|
|
172
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({}, props)));
|
|
173
|
+
await waitFor(() => {
|
|
174
|
+
expect(screen.getByText(initialStep.title)).toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
await user.click(screen.getByText('Red'));
|
|
177
|
+
await waitFor(() => {
|
|
178
|
+
expect(httpClient).toHaveBeenCalledTimes(1);
|
|
179
|
+
});
|
|
180
|
+
await user.click(screen.getByText('Blue'));
|
|
181
|
+
await waitFor(() => {
|
|
182
|
+
expect(screen.getByText('Refreshed after No-Op')).toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
expect(httpClient).toHaveBeenCalledTimes(2);
|
|
185
|
+
expect(httpClient).toHaveBeenCalledWith('/refresh', expect.objectContaining({ body: '{"color":"blue"}' }));
|
|
186
|
+
await user.click(screen.getByText('Submit step'));
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
expect(httpClient).toHaveBeenCalledTimes(3);
|
|
189
|
+
});
|
|
190
|
+
expect(httpClient).toHaveBeenCalledWith('/step-submit', expect.objectContaining({ body: '{"color":"blue"}' }));
|
|
191
|
+
expect(props.onError).not.toHaveBeenCalled();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -3,11 +3,11 @@ import { act, screen, waitFor } from '@testing-library/react';
|
|
|
3
3
|
import { userEvent } from '@testing-library/user-event';
|
|
4
4
|
import { Button } from '@transferwise/components';
|
|
5
5
|
import { useState } from 'react';
|
|
6
|
+
import { vi } from 'vitest';
|
|
6
7
|
import { renderWithProviders, respondWith } from '../test-utils';
|
|
7
|
-
import { abortableDelay, respondWithDelay } from '../test-utils/fetch-utils';
|
|
8
8
|
import DynamicFlowWise from '../test-utils/DynamicFlowWise';
|
|
9
|
+
import { abortableDelay, respondWithDelay } from '../test-utils/fetch-utils';
|
|
9
10
|
import { createEmptyStep } from '../test-utils/step-utils';
|
|
10
|
-
import { vi } from 'vitest';
|
|
11
11
|
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
|
|
12
12
|
const emptyStep = createEmptyStep();
|
|
13
13
|
const getDefaultProps = () => ({
|
|
@@ -16,6 +16,7 @@ const getDefaultProps = () => ({
|
|
|
16
16
|
onError: vi.fn(),
|
|
17
17
|
onEvent: vi.fn(),
|
|
18
18
|
onLog: vi.fn(),
|
|
19
|
+
onLink: vi.fn(),
|
|
19
20
|
});
|
|
20
21
|
const getStep = (model, schema) => {
|
|
21
22
|
var _a;
|
|
@@ -158,9 +159,7 @@ describe('Data submission', () => {
|
|
|
158
159
|
});
|
|
159
160
|
expect(screen.getByText('Please select a currency.')).toBeInTheDocument();
|
|
160
161
|
});
|
|
161
|
-
|
|
162
|
-
const model = { name: 'Name', number: '12345' };
|
|
163
|
-
const step = getStep(model, schema);
|
|
162
|
+
describe('loading state', () => {
|
|
164
163
|
const httpClient = vi.fn().mockImplementation(async (input, init) => {
|
|
165
164
|
var _a;
|
|
166
165
|
return abortableDelay(1, (_a = init === null || init === void 0 ? void 0 : init.signal) !== null && _a !== void 0 ? _a : null).then(async () => respondWith({
|
|
@@ -172,16 +171,164 @@ describe('Data submission', () => {
|
|
|
172
171
|
schemas: [],
|
|
173
172
|
}));
|
|
174
173
|
});
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
expect(
|
|
174
|
+
it('should set loading state on action buttons that were clicked (and set disabled on other buttons) while submitting, and then re-enable them', async () => {
|
|
175
|
+
const model = { name: 'Name', number: '12345' };
|
|
176
|
+
const step = getStep(model, schema);
|
|
177
|
+
const props = getDefaultProps();
|
|
178
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({ httpClient: httpClient, initialStep: step }, props)));
|
|
179
|
+
const submitButton = screen.getByRole('button', { name: 'Submit' });
|
|
180
|
+
const otherButton = screen.getByRole('button', { name: 'Other' });
|
|
181
|
+
await user.click(submitButton);
|
|
182
|
+
expect(submitButton).toHaveAttribute('aria-busy', 'true');
|
|
183
|
+
expect(otherButton).toBeDisabled();
|
|
184
|
+
expect(otherButton).not.toHaveAttribute('aria-busy');
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
expect(screen.getByText('Next step')).toBeInTheDocument();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('when attempting submission with one button, but validation fails, then submissting with another button', () => {
|
|
190
|
+
it('should only set the loading state of the button that was last pressed', async () => {
|
|
191
|
+
const step = {
|
|
192
|
+
id: 'step-id',
|
|
193
|
+
title: 'Submission test',
|
|
194
|
+
layout: [
|
|
195
|
+
{ type: 'box', components: [{ type: 'form', schemaId: '#schema' }] },
|
|
196
|
+
{
|
|
197
|
+
type: 'button',
|
|
198
|
+
title: 'Submit',
|
|
199
|
+
action: { id: 'submit', url: '/submit', method: 'POST' },
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: 'button',
|
|
203
|
+
title: 'Submit without validation',
|
|
204
|
+
action: {
|
|
205
|
+
id: 'submit-without-validation',
|
|
206
|
+
url: '/submit',
|
|
207
|
+
method: 'POST',
|
|
208
|
+
skipValidation: true,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
refreshUrl: '/refresh',
|
|
213
|
+
schemas: [
|
|
214
|
+
{
|
|
215
|
+
$id: '#schema',
|
|
216
|
+
title: 'Name',
|
|
217
|
+
type: 'string',
|
|
218
|
+
validationMessages: { required: 'Please enter your name!' },
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
const props = getDefaultProps();
|
|
223
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({ httpClient: httpClient, initialStep: step }, props)));
|
|
224
|
+
const submitButton = screen.getByRole('button', { name: 'Submit' });
|
|
225
|
+
const submitWithoutValidationButton = screen.getByRole('button', {
|
|
226
|
+
name: 'Submit without validation',
|
|
227
|
+
});
|
|
228
|
+
await user.click(submitButton);
|
|
229
|
+
expect(screen.getByText('Please enter your name!')).toBeInTheDocument();
|
|
230
|
+
await user.click(submitWithoutValidationButton);
|
|
231
|
+
// expect(screen.queryByText('Please enter your name!')).not.toBeInTheDocument();
|
|
232
|
+
expect(submitWithoutValidationButton).toHaveAttribute('aria-busy', 'true');
|
|
233
|
+
expect(submitButton).toBeDisabled();
|
|
234
|
+
expect(submitButton).not.toHaveAttribute('aria-busy');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
describe('link buttons', () => {
|
|
238
|
+
it('should NOT set loading state on link buttons that have been previously clicked', async () => {
|
|
239
|
+
var _a;
|
|
240
|
+
const model = { name: 'Name', number: '12345' };
|
|
241
|
+
const step = {
|
|
242
|
+
id: 'step-id',
|
|
243
|
+
title: 'Submission test',
|
|
244
|
+
layout: [
|
|
245
|
+
{ type: 'box', components: [{ type: 'form', schemaId: (_a = schema.$id) !== null && _a !== void 0 ? _a : '' }] },
|
|
246
|
+
{
|
|
247
|
+
type: 'button',
|
|
248
|
+
title: 'Submit',
|
|
249
|
+
behavior: {
|
|
250
|
+
type: 'action',
|
|
251
|
+
action: { id: 'action-id', url: '/submit', method: 'POST' },
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
type: 'button',
|
|
256
|
+
title: 'Link',
|
|
257
|
+
behavior: { type: 'link', url: 'https://www.google.com' },
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
schemas: [schema],
|
|
261
|
+
model,
|
|
262
|
+
};
|
|
263
|
+
const props = getDefaultProps();
|
|
264
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({ httpClient: httpClient, initialStep: step }, props)));
|
|
265
|
+
const submitButton = screen.getByRole('button', { name: 'Submit' });
|
|
266
|
+
const linkButton = screen.getByRole('button', { name: 'Link' });
|
|
267
|
+
await user.click(linkButton);
|
|
268
|
+
expect(props.onLink).toHaveBeenCalled();
|
|
269
|
+
await user.click(submitButton);
|
|
270
|
+
expect(submitButton).toHaveAttribute('aria-busy', 'true');
|
|
271
|
+
expect(linkButton).toBeDisabled();
|
|
272
|
+
expect(linkButton).not.toHaveAttribute('aria-busy');
|
|
273
|
+
await waitFor(() => {
|
|
274
|
+
expect(screen.getByText('Next step')).toBeInTheDocument();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
describe('buttons that trigger modals', () => {
|
|
279
|
+
it('should NOT set loading state on buttons that trigger modals that have been previously clicked', async () => {
|
|
280
|
+
var _a;
|
|
281
|
+
const model = { name: 'Name', number: '12345' };
|
|
282
|
+
const step = {
|
|
283
|
+
id: 'step-id',
|
|
284
|
+
title: 'Submission test',
|
|
285
|
+
layout: [
|
|
286
|
+
{ type: 'box', components: [{ type: 'form', schemaId: (_a = schema.$id) !== null && _a !== void 0 ? _a : '' }] },
|
|
287
|
+
{
|
|
288
|
+
type: 'button',
|
|
289
|
+
title: 'Submit',
|
|
290
|
+
behavior: {
|
|
291
|
+
type: 'action',
|
|
292
|
+
action: { id: 'action-id', url: '/submit', method: 'POST' },
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
type: 'button',
|
|
297
|
+
title: 'Open Modal',
|
|
298
|
+
behavior: {
|
|
299
|
+
type: 'modal',
|
|
300
|
+
title: 'Open Modal',
|
|
301
|
+
content: [
|
|
302
|
+
{
|
|
303
|
+
type: 'paragraph',
|
|
304
|
+
text: 'This is a modal opened by the submit button.',
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
schemas: [schema],
|
|
311
|
+
model,
|
|
312
|
+
};
|
|
313
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({ httpClient: httpClient, initialStep: step }, getDefaultProps())));
|
|
314
|
+
const submitButton = screen.getByRole('button', { name: 'Submit' });
|
|
315
|
+
const modalButton = screen.getByRole('button', { name: 'Open Modal' });
|
|
316
|
+
await user.click(modalButton);
|
|
317
|
+
await waitFor(() => {
|
|
318
|
+
expect(screen.getByText('This is a modal opened by the submit button.')).toBeInTheDocument();
|
|
319
|
+
});
|
|
320
|
+
await user.keyboard('{Escape}');
|
|
321
|
+
await waitFor(() => {
|
|
322
|
+
expect(screen.queryByText('This is a modal opened by the submit button.')).not.toBeInTheDocument();
|
|
323
|
+
});
|
|
324
|
+
await user.click(submitButton);
|
|
325
|
+
expect(submitButton).toHaveAttribute('aria-busy', 'true');
|
|
326
|
+
expect(modalButton).toBeDisabled();
|
|
327
|
+
expect(modalButton).not.toHaveAttribute('aria-busy');
|
|
328
|
+
await waitFor(() => {
|
|
329
|
+
expect(screen.getByText('Next step')).toBeInTheDocument();
|
|
330
|
+
});
|
|
331
|
+
});
|
|
185
332
|
});
|
|
186
333
|
});
|
|
187
334
|
describe('with unreferenced schemas', () => {
|
|
@@ -345,9 +492,7 @@ describe('Data submission', () => {
|
|
|
345
492
|
const initialStep = {
|
|
346
493
|
id: 'step-id',
|
|
347
494
|
title: 'Footer submission test',
|
|
348
|
-
layout: [
|
|
349
|
-
{ type: 'box', components: [{ type: 'form', schemaId: '#schema' }] },
|
|
350
|
-
],
|
|
495
|
+
layout: [{ type: 'box', components: [{ type: 'form', schemaId: '#schema' }] }],
|
|
351
496
|
footer: [
|
|
352
497
|
{ type: 'form', schemaId: '#footer-schema' },
|
|
353
498
|
{
|
|
@@ -43,12 +43,11 @@ describe('Upsell component', () => {
|
|
|
43
43
|
expect(getCloseButton()).not.toBeInTheDocument();
|
|
44
44
|
});
|
|
45
45
|
describe('when the call to action is clicked', () => {
|
|
46
|
-
it('triggers the specified behavior',
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
expect(onLink).toHaveBeenCalledWith('/upgrade');
|
|
46
|
+
it('triggers the specified behavior (or for a link, has the correct attributes)', () => {
|
|
47
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({}, props)));
|
|
48
|
+
const link = screen.getByRole('link', { name: /upgrade now/i });
|
|
49
|
+
expect(link).toHaveAttribute('href', '/upgrade');
|
|
50
|
+
expect(link).toHaveAttribute('target', '_blank');
|
|
52
51
|
});
|
|
53
52
|
});
|
|
54
53
|
});
|
|
@@ -123,4 +122,33 @@ describe('Upsell component', () => {
|
|
|
123
122
|
});
|
|
124
123
|
});
|
|
125
124
|
});
|
|
125
|
+
describe('when the upsell has a link behavior', () => {
|
|
126
|
+
it('does not trigger twice (the renderer handles the href, so the onLink is not called)', async () => {
|
|
127
|
+
const onLink = vi.fn();
|
|
128
|
+
const props = getProps({
|
|
129
|
+
id: 'step-1',
|
|
130
|
+
title: 'Step 1',
|
|
131
|
+
schemas: [],
|
|
132
|
+
layout: [
|
|
133
|
+
{
|
|
134
|
+
type: 'upsell',
|
|
135
|
+
text: 'Try our premium features!',
|
|
136
|
+
callToAction: {
|
|
137
|
+
title: 'Upgrade now',
|
|
138
|
+
behavior: {
|
|
139
|
+
type: 'link',
|
|
140
|
+
url: '/upgrade',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
renderWithProviders(_jsx(DynamicFlowWise, Object.assign({}, props, { onLink: onLink })));
|
|
147
|
+
const link = screen.getByRole('link');
|
|
148
|
+
expect(link).toHaveAttribute('href', '/upgrade');
|
|
149
|
+
expect(link).toHaveAttribute('target', '_blank');
|
|
150
|
+
await user.click(link);
|
|
151
|
+
expect(onLink).not.toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
126
154
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FlowController.d.ts","sourceRoot":"","sources":["../../../src/controller/FlowController.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAA6B,MAAM,0CAA0C,CAAC;AAMjG,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAKL,aAAa,EACb,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAShD,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,oBAAoB,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG;IACvF,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,YAAY,CAAC;IACvB,wBAAwB,EAAE,wBAAwB,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAClC,CAAC;AAIF,eAAO,MAAM,oBAAoB,GAAI,OAAO,mBAAmB;;;;
|
|
1
|
+
{"version":3,"file":"FlowController.d.ts","sourceRoot":"","sources":["../../../src/controller/FlowController.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAA6B,MAAM,0CAA0C,CAAC;AAMjG,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAKL,aAAa,EACb,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAShD,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,oBAAoB,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG;IACvF,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,YAAY,CAAC;IACvB,wBAAwB,EAAE,wBAAwB,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAClC,CAAC;AAIF,eAAO,MAAM,oBAAoB,GAAI,OAAO,mBAAmB;;;;CAgc9D,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executeRefresh.d.ts","sourceRoot":"","sources":["../../../src/controller/executeRefresh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAC;AACpF,OAAO,KAAK,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"executeRefresh.d.ts","sourceRoot":"","sources":["../../../src/controller/executeRefresh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAC;AACpF,OAAO,KAAK,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AASlG,KAAK,OAAO,GACR;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,eAAO,MAAM,cAAc,GAAU,OAAO;IAC1C,WAAW,EAAE,WAAW,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,EAAE,OAAO,KAAK,CAAC;IACzB,UAAU,EAAE,wBAAwB,CAAC;IACrC,QAAQ,EAAE,sBAAsB,CAAC;CAClC,KAAG,OAAO,CAAC,OAAO,CA4DlB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executeRequest.d.ts","sourceRoot":"","sources":["../../../src/controller/executeRequest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAC;AACxF,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAU7F,OAAO,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAIzE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,KAAK,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC3C,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;KACzB,CAAC;IACF,SAAS,CAAC,EAAE;QACV,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;KACzB,CAAC;CACH,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"executeRequest.d.ts","sourceRoot":"","sources":["../../../src/controller/executeRequest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAC;AACxF,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAU7F,OAAO,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAIzE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,KAAK,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC3C,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;KACzB,CAAC;IACF,SAAS,CAAC,EAAE;QACV,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;KACzB,CAAC;CACH,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;CACpB,GACD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,eAAO,MAAM,cAAc,GAAU,OAAO;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,OAAO,KAAK,CAAC;IACzB,UAAU,EAAE,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,EAAE,sBAAsB,CAAC;CAClC,KAAG,OAAO,CAAC,OAAO,CA+ElB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executeSubmission.d.ts","sourceRoot":"","sources":["../../../src/controller/executeSubmission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAGzE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,OAAO,EAAkB,MAAM,kBAAkB,CAAC;AAE3D,eAAO,MAAM,iBAAiB,GAAU,OAAO;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,EAAE,sBAAsB,CAAC;CAClC,KAAG,OAAO,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"executeSubmission.d.ts","sourceRoot":"","sources":["../../../src/controller/executeSubmission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAGzE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,OAAO,EAAkB,MAAM,kBAAkB,CAAC;AAE3D,eAAO,MAAM,iBAAiB,GAAU,OAAO;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,EAAE,sBAAsB,CAAC;CAClC,KAAG,OAAO,CAAC,OAAO,CA2ElB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,MAAM,EAAE,OAAO,KAAK,KAAG,OAKrE,CAAC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { JsonElement } from '@wise/dynamic-flow-types/spec';
|
|
2
|
-
declare const responseTypes: readonly ["step", "action", "exit", "modal", "subflow"];
|
|
2
|
+
declare const responseTypes: readonly ["step", "action", "exit", "modal", "subflow", "no-op"];
|
|
3
3
|
export type ResponseType = (typeof responseTypes)[number];
|
|
4
4
|
/**
|
|
5
|
-
* Returns either 'step', 'action', or '
|
|
5
|
+
* Returns either 'step', 'action', 'exit', 'modal', 'subflow', or 'no-op' based on the response headers and body.
|
|
6
6
|
* This function takes a response body parameter because, for legacy reasons, we still need to consider the body
|
|
7
7
|
* to determine the response type, in cases where the response headers are not set.
|
|
8
8
|
* Ideally it should just be a matter of checking the "X-Df-Response-Type" response header.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getResponseType.d.ts","sourceRoot":"","sources":["../../../src/controller/getResponseType.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAG5D,QAAA,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"getResponseType.d.ts","sourceRoot":"","sources":["../../../src/controller/getResponseType.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAG5D,QAAA,MAAM,aAAa,kEAAmE,CAAC;AACvF,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,EAAE,MAAM,WAAW,KAAG,YAYrE,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,SAAS,OAAO,KAAG,YAAY,GAAG,SAY3E,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wise/dynamic-flow-client",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.10.0",
|
|
4
4
|
"description": "Dynamic Flow web client",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./build/main.js",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"typescript": "5.9.3",
|
|
74
74
|
"vitest": "4.0.18",
|
|
75
75
|
"vitest-fetch-mock": "0.4.5",
|
|
76
|
-
"@wise/dynamic-flow-
|
|
77
|
-
"@wise/dynamic-flow-
|
|
76
|
+
"@wise/dynamic-flow-renderers": "0.0.0",
|
|
77
|
+
"@wise/dynamic-flow-fixtures": "0.0.1"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
80
|
"@transferwise/components": "^46.104.0",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"react-intl": "^6 || ^7"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@wise/dynamic-flow-types": "4.
|
|
90
|
+
"@wise/dynamic-flow-types": "4.10.0"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
93
|
"dev": "EXCLUDE_VISUAL_TESTS=true pnpm storybook dev -p 3003",
|