@paypal/checkout-components 5.0.405-alpha-c03e09a.0 → 5.0.405
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/dist/button.js +1 -1
- package/dist/test/button.js +1 -1
- package/package.json +1 -1
- package/src/funding/common.jsx +0 -1
- package/src/funding/content.jsx +0 -9
- package/src/funding/paypal/template.jsx +1 -17
- package/src/lib/appSwitchResume.js +78 -23
- package/src/lib/appSwithResume.test.js +332 -3
- package/src/types.js +0 -1
- package/src/ui/buttons/button.jsx +1 -12
- package/src/ui/buttons/props.js +0 -58
- package/src/ui/buttons/props.test.js +1 -97
- package/src/zoid/qr-code/component.test.js +46 -0
- package/src/zoid/qr-code/container.jsx +2 -2
- package/src/zoid/qr-code/container.test.jsx +187 -0
- package/src/zoid/qr-code/prerender.jsx +5 -5
- package/src/zoid/qr-code/prerender.test.jsx +108 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { describe, expect, it, beforeEach, vi } from "vitest";
|
|
3
3
|
import { FUNDING } from "@paypal/sdk-constants";
|
|
4
4
|
|
|
5
|
-
import { BUTTON_COLOR
|
|
5
|
+
import { BUTTON_COLOR } from "../../constants";
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
getButtonColor,
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
hasInvalidScriptOptionsForFullRedesign,
|
|
15
15
|
determineRandomButtonColor,
|
|
16
16
|
getColorABTestFromStorage,
|
|
17
|
-
getCobrandedBNPLLabelFlags,
|
|
18
17
|
} from "./props";
|
|
19
18
|
|
|
20
19
|
describe("getColorABTestFromStorage", () => {
|
|
@@ -865,98 +864,3 @@ describe("HideSubmitButtonProps type validation", () => {
|
|
|
865
864
|
expect(validButtonProps.hideSubmitButtonForCardForm).toBe(false);
|
|
866
865
|
});
|
|
867
866
|
});
|
|
868
|
-
|
|
869
|
-
describe("getCobrandedBNPLLabelFlags", () => {
|
|
870
|
-
// $FlowFixMe - test object intentionally omits non-relevant ButtonPropsInputs fields
|
|
871
|
-
const eligibleProps = {
|
|
872
|
-
fundingSource: FUNDING.PAYPAL,
|
|
873
|
-
fundingEligibility: {
|
|
874
|
-
paylater: { eligible: true },
|
|
875
|
-
},
|
|
876
|
-
experiment: { isPaylaterCobrandedLabelEnabled: true },
|
|
877
|
-
locale: { lang: "en", country: "US" },
|
|
878
|
-
style: {},
|
|
879
|
-
flow: BUTTON_FLOW.PURCHASE,
|
|
880
|
-
};
|
|
881
|
-
|
|
882
|
-
it("should return true when all conditions are met", () => {
|
|
883
|
-
const { isPayNowOrLaterLabelEligible, shouldApplyPayNowOrLaterLabel } =
|
|
884
|
-
// $FlowFixMe
|
|
885
|
-
getCobrandedBNPLLabelFlags(eligibleProps);
|
|
886
|
-
|
|
887
|
-
expect(isPayNowOrLaterLabelEligible).toBe(true);
|
|
888
|
-
expect(shouldApplyPayNowOrLaterLabel).toBe(true);
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
it("should return false when experiment flag is disabled", () => {
|
|
892
|
-
const { isPayNowOrLaterLabelEligible } =
|
|
893
|
-
// $FlowFixMe
|
|
894
|
-
getCobrandedBNPLLabelFlags({
|
|
895
|
-
...eligibleProps,
|
|
896
|
-
experiment: { isPaylaterCobrandedLabelEnabled: false },
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
900
|
-
});
|
|
901
|
-
|
|
902
|
-
it("should return false when paylater is not eligible", () => {
|
|
903
|
-
const { isPayNowOrLaterLabelEligible } =
|
|
904
|
-
// $FlowFixMe
|
|
905
|
-
getCobrandedBNPLLabelFlags({
|
|
906
|
-
...eligibleProps,
|
|
907
|
-
fundingEligibility: { paylater: { eligible: false } },
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
it("should return false when fundingSource is not PAYPAL or undefined", () => {
|
|
914
|
-
const { isPayNowOrLaterLabelEligible } =
|
|
915
|
-
// $FlowFixMe
|
|
916
|
-
getCobrandedBNPLLabelFlags({
|
|
917
|
-
...eligibleProps,
|
|
918
|
-
fundingSource: FUNDING.VENMO,
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
it("should return false when a non-paypal label is set", () => {
|
|
925
|
-
const { isPayNowOrLaterLabelEligible } =
|
|
926
|
-
// $FlowFixMe
|
|
927
|
-
getCobrandedBNPLLabelFlags({
|
|
928
|
-
...eligibleProps,
|
|
929
|
-
style: { label: "checkout" },
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
933
|
-
});
|
|
934
|
-
|
|
935
|
-
it("should return true when label is explicitly set to paypal", () => {
|
|
936
|
-
const { isPayNowOrLaterLabelEligible } =
|
|
937
|
-
// $FlowFixMe
|
|
938
|
-
getCobrandedBNPLLabelFlags({
|
|
939
|
-
...eligibleProps,
|
|
940
|
-
style: { label: "paypal" },
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
expect(isPayNowOrLaterLabelEligible).toBe(true);
|
|
944
|
-
});
|
|
945
|
-
|
|
946
|
-
it("should return false when locale does not have PayNowOrLater content", () => {
|
|
947
|
-
const { isPayNowOrLaterLabelEligible } =
|
|
948
|
-
// $FlowFixMe
|
|
949
|
-
getCobrandedBNPLLabelFlags({
|
|
950
|
-
...eligibleProps,
|
|
951
|
-
locale: { lang: "fr", country: "FR" },
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
it("should return false when props is null", () => {
|
|
958
|
-
const { isPayNowOrLaterLabelEligible } = getCobrandedBNPLLabelFlags(null);
|
|
959
|
-
|
|
960
|
-
expect(isPayNowOrLaterLabelEligible).toBe(false);
|
|
961
|
-
});
|
|
962
|
-
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for QR Code component creation and configuration.
|
|
4
|
+
* Verifies component initialization, memoization, and proper setup of SDK dependencies.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test, vi } from "vitest";
|
|
8
|
+
|
|
9
|
+
import { getQRCodeComponent } from "./component";
|
|
10
|
+
|
|
11
|
+
vi.mock("@paypal/sdk-client/src", () => ({
|
|
12
|
+
getLogger: vi.fn(() => ({
|
|
13
|
+
metric: vi.fn().mockReturnThis(),
|
|
14
|
+
error: vi.fn().mockReturnThis(),
|
|
15
|
+
track: vi.fn().mockReturnThis(),
|
|
16
|
+
flush: vi.fn().mockReturnThis(),
|
|
17
|
+
metricCounter: vi.fn().mockReturnThis(),
|
|
18
|
+
})),
|
|
19
|
+
getPayPalDomainRegex: vi.fn(() => /paypal\.com/),
|
|
20
|
+
getPayPalDomain: vi.fn(() => "https://www.paypal.com"),
|
|
21
|
+
getCSPNonce: vi.fn(() => "mock-nonce"),
|
|
22
|
+
getSDKMeta: vi.fn(() => "mock-sdk-meta"),
|
|
23
|
+
getDebug: vi.fn(() => false),
|
|
24
|
+
getEnv: vi.fn(() => "test"),
|
|
25
|
+
getSessionID: vi.fn(() => "mock-session-id"),
|
|
26
|
+
getLocale: vi.fn(() => ({ country: "US", lang: "en" })),
|
|
27
|
+
getClientID: vi.fn(() => "mock-client-id"),
|
|
28
|
+
getCorrelationID: vi.fn(() => "mock-correlation-id"),
|
|
29
|
+
getBuyerCountry: vi.fn(() => "US"),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
describe("getQRCodeComponent", () => {
|
|
33
|
+
test("should create Zoid component", () => {
|
|
34
|
+
const component = getQRCodeComponent();
|
|
35
|
+
|
|
36
|
+
expect(component).toBeDefined();
|
|
37
|
+
expect(typeof component).toBe("function");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should memoize component instance", () => {
|
|
41
|
+
const component1 = getQRCodeComponent();
|
|
42
|
+
const component2 = getQRCodeComponent();
|
|
43
|
+
|
|
44
|
+
expect(component1).toBe(component2);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { destroyElement, type EventEmitterType } from "@krakenjs/belter/src";
|
|
5
5
|
import { EVENT, type RenderOptionsType } from "@krakenjs/zoid/src";
|
|
6
6
|
import { node, dom, type ChildType } from "@krakenjs/jsx-pragmatic/src";
|
|
7
|
+
import { getCSPNonce } from "@paypal/sdk-client/src";
|
|
7
8
|
|
|
8
9
|
import { type QRCodeProps } from "./types";
|
|
9
10
|
|
|
@@ -113,7 +114,6 @@ export function QRCodeContainer({
|
|
|
113
114
|
export function containerTemplate({
|
|
114
115
|
frame,
|
|
115
116
|
prerenderFrame,
|
|
116
|
-
props,
|
|
117
117
|
doc,
|
|
118
118
|
uid,
|
|
119
119
|
event,
|
|
@@ -122,7 +122,7 @@ export function containerTemplate({
|
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
const
|
|
125
|
+
const cspNonce = __WEB__ ? getCSPNonce() : undefined;
|
|
126
126
|
|
|
127
127
|
return (
|
|
128
128
|
<QRCodeContainer
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
/** @jsx node */
|
|
3
|
+
/**
|
|
4
|
+
* Unit tests for QR Code container template and component.
|
|
5
|
+
* Tests CSP nonce handling, frame validation, and visibility state management
|
|
6
|
+
* for both web and non-web environments.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test, vi, beforeEach, afterEach } from "vitest";
|
|
10
|
+
import { dom } from "@krakenjs/jsx-pragmatic/src";
|
|
11
|
+
import { getCSPNonce } from "@paypal/sdk-client/src";
|
|
12
|
+
|
|
13
|
+
import { containerTemplate, QRCodeContainer } from "./container";
|
|
14
|
+
|
|
15
|
+
const TEST_UID = "test-uid-12345";
|
|
16
|
+
const TEST_NONCE = "test-csp-nonce-xyz";
|
|
17
|
+
|
|
18
|
+
vi.mock("@paypal/sdk-client/src", () => ({
|
|
19
|
+
getCSPNonce: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const createMocks = () => ({
|
|
23
|
+
frame: document.createElement("iframe"),
|
|
24
|
+
prerenderFrame: document.createElement("iframe"),
|
|
25
|
+
// $FlowFixMe - mock event emitter for tests
|
|
26
|
+
event: {
|
|
27
|
+
on: vi.fn(),
|
|
28
|
+
trigger: vi.fn(),
|
|
29
|
+
once: vi.fn(),
|
|
30
|
+
reset: vi.fn(),
|
|
31
|
+
triggerOnce: vi.fn(),
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const originalWebValue = global.__WEB__;
|
|
36
|
+
|
|
37
|
+
const setupTest = () => {
|
|
38
|
+
vi.clearAllMocks();
|
|
39
|
+
global.__WEB__ = true;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const teardownTest = () => {
|
|
43
|
+
global.__WEB__ = originalWebValue;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe("containerTemplate", () => {
|
|
47
|
+
beforeEach(setupTest);
|
|
48
|
+
afterEach(teardownTest);
|
|
49
|
+
|
|
50
|
+
test.each([[true], [false]])(
|
|
51
|
+
"when __WEB__ is %s, should call getCSPNonce conditionally",
|
|
52
|
+
(webValue) => {
|
|
53
|
+
global.__WEB__ = webValue;
|
|
54
|
+
// $FlowIssue - mock return value
|
|
55
|
+
getCSPNonce.mockReturnValue(TEST_NONCE);
|
|
56
|
+
|
|
57
|
+
const { frame, prerenderFrame, event } = createMocks();
|
|
58
|
+
// $FlowIssue - test mock parameters
|
|
59
|
+
const result = containerTemplate({
|
|
60
|
+
frame,
|
|
61
|
+
prerenderFrame,
|
|
62
|
+
doc: document,
|
|
63
|
+
uid: TEST_UID,
|
|
64
|
+
event,
|
|
65
|
+
// $FlowIssue - test mock parameters
|
|
66
|
+
props: {},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (webValue) {
|
|
70
|
+
expect(getCSPNonce).toHaveBeenCalled();
|
|
71
|
+
// Verify nonce value is available for use
|
|
72
|
+
expect(getCSPNonce).toHaveReturnedWith(TEST_NONCE);
|
|
73
|
+
expect(result).toBeDefined();
|
|
74
|
+
} else {
|
|
75
|
+
expect(getCSPNonce).not.toHaveBeenCalled();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
test.each([
|
|
81
|
+
[
|
|
82
|
+
"frame",
|
|
83
|
+
{ frame: null, prerenderFrame: document.createElement("iframe") },
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
"prerenderFrame",
|
|
87
|
+
{ frame: document.createElement("iframe"), prerenderFrame: null },
|
|
88
|
+
],
|
|
89
|
+
])("should return undefined when %s is missing", (_paramName, frames) => {
|
|
90
|
+
// $FlowIssue - test props
|
|
91
|
+
const result = containerTemplate({
|
|
92
|
+
...frames,
|
|
93
|
+
doc: document,
|
|
94
|
+
uid: TEST_UID,
|
|
95
|
+
event: createMocks().event,
|
|
96
|
+
props: {},
|
|
97
|
+
});
|
|
98
|
+
expect(result).toBeUndefined();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("should apply cspNonce to style element in rendered output", () => {
|
|
102
|
+
// $FlowIssue - mock return value
|
|
103
|
+
getCSPNonce.mockReturnValue(TEST_NONCE);
|
|
104
|
+
|
|
105
|
+
const { frame, prerenderFrame, event } = createMocks();
|
|
106
|
+
// $FlowIssue - test mock parameters
|
|
107
|
+
const result = containerTemplate({
|
|
108
|
+
frame,
|
|
109
|
+
prerenderFrame,
|
|
110
|
+
doc: document,
|
|
111
|
+
uid: TEST_UID,
|
|
112
|
+
event,
|
|
113
|
+
// $FlowIssue - test mock parameters
|
|
114
|
+
props: {},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// $FlowFixMe - result is HTMLElement in test context
|
|
118
|
+
const styleElement = result.querySelector("style");
|
|
119
|
+
expect(styleElement?.getAttribute("nonce")).toBe(TEST_NONCE);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("QRCodeContainer", () => {
|
|
124
|
+
beforeEach(setupTest);
|
|
125
|
+
afterEach(teardownTest);
|
|
126
|
+
|
|
127
|
+
test.each([
|
|
128
|
+
["frame", (mocks) => ({ ...mocks, frame: null })],
|
|
129
|
+
["prerenderFrame", (mocks) => ({ ...mocks, prerenderFrame: null })],
|
|
130
|
+
])("should throw error when %s is missing", (_paramName, modifyMocks) => {
|
|
131
|
+
const mocks = createMocks();
|
|
132
|
+
// $FlowIssue - intentionally passing null for error test
|
|
133
|
+
expect(() =>
|
|
134
|
+
QRCodeContainer({ uid: TEST_UID, ...modifyMocks(mocks) })
|
|
135
|
+
).toThrow("Expected frame and prerenderframe");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("should set up visibility classes for prerender transition", () => {
|
|
139
|
+
// Initial state: prerenderFrame visible, component frame invisible
|
|
140
|
+
const { frame, prerenderFrame, event } = createMocks();
|
|
141
|
+
QRCodeContainer({ uid: TEST_UID, frame, prerenderFrame, event });
|
|
142
|
+
|
|
143
|
+
// Verify component frame is initially hidden
|
|
144
|
+
expect(frame.classList.contains("component-frame")).toBe(true);
|
|
145
|
+
expect(frame.classList.contains("invisible")).toBe(true);
|
|
146
|
+
|
|
147
|
+
// Verify prerender frame is initially visible
|
|
148
|
+
expect(prerenderFrame.classList.contains("prerender-frame")).toBe(true);
|
|
149
|
+
expect(prerenderFrame.classList.contains("visible")).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("should toggle frame visibility when EVENT.RENDERED fires", () => {
|
|
153
|
+
const mocks = createMocks();
|
|
154
|
+
QRCodeContainer({ uid: TEST_UID, ...mocks });
|
|
155
|
+
|
|
156
|
+
// Extract and invoke the registered handler
|
|
157
|
+
// $FlowFixMe - accessing vitest mock properties
|
|
158
|
+
const handler = mocks.event.on.mock.calls[0][1];
|
|
159
|
+
handler();
|
|
160
|
+
|
|
161
|
+
// Verify visibility toggle: prerenderFrame becomes invisible
|
|
162
|
+
expect(mocks.prerenderFrame.classList.contains("invisible")).toBe(true);
|
|
163
|
+
expect(mocks.prerenderFrame.classList.contains("visible")).toBe(false);
|
|
164
|
+
|
|
165
|
+
// Verify visibility toggle: component frame becomes visible
|
|
166
|
+
expect(mocks.frame.classList.contains("visible")).toBe(true);
|
|
167
|
+
expect(mocks.frame.classList.contains("invisible")).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("should pass cspNonce to QRCodeContainer and apply to rendered style", () => {
|
|
171
|
+
const { frame, prerenderFrame, event } = createMocks();
|
|
172
|
+
const result = QRCodeContainer({
|
|
173
|
+
uid: TEST_UID,
|
|
174
|
+
frame,
|
|
175
|
+
prerenderFrame,
|
|
176
|
+
event,
|
|
177
|
+
cspNonce: TEST_NONCE,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Render to DOM to access style element
|
|
181
|
+
// $FlowFixMe - result is ChildType with render method
|
|
182
|
+
const rendered = result.render(dom({ doc: document }));
|
|
183
|
+
const styleElement = rendered.querySelector("style");
|
|
184
|
+
|
|
185
|
+
expect(styleElement?.getAttribute("nonce")).toBe(TEST_NONCE);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
import { type RenderOptionsType } from "@krakenjs/zoid/src";
|
|
5
5
|
import { node, dom } from "@krakenjs/jsx-pragmatic/src";
|
|
6
6
|
import { SpinnerPage } from "@paypal/common-components/src";
|
|
7
|
+
import { getCSPNonce } from "@paypal/sdk-client/src";
|
|
7
8
|
|
|
8
9
|
import { type QRCodeProps } from "./types";
|
|
9
10
|
|
|
10
11
|
export function prerenderTemplate({
|
|
11
12
|
doc,
|
|
12
|
-
props,
|
|
13
13
|
close,
|
|
14
14
|
}: RenderOptionsType<QRCodeProps>): ?HTMLElement {
|
|
15
|
+
const cspNonce = __WEB__ ? getCSPNonce() : undefined;
|
|
16
|
+
|
|
15
17
|
const style = `
|
|
16
18
|
#close {
|
|
17
19
|
position: absolute;
|
|
@@ -42,11 +44,9 @@ export function prerenderTemplate({
|
|
|
42
44
|
`;
|
|
43
45
|
|
|
44
46
|
const children = [
|
|
45
|
-
<style nonce={
|
|
47
|
+
<style nonce={cspNonce} innerHTML={style} />,
|
|
46
48
|
<a href="#" id="close" aria-label="close" role="button" onClick={close} />,
|
|
47
49
|
];
|
|
48
50
|
|
|
49
|
-
return new SpinnerPage({ nonce:
|
|
50
|
-
dom({ doc })
|
|
51
|
-
);
|
|
51
|
+
return new SpinnerPage({ nonce: cspNonce }, children).render(dom({ doc }));
|
|
52
52
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
/** @jsx node */
|
|
3
|
+
/**
|
|
4
|
+
* Unit tests for QR Code prerender template.
|
|
5
|
+
* Tests CSP nonce propagation to SpinnerPage and style elements
|
|
6
|
+
* in both web and non-web environments.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test, vi, beforeEach, afterEach } from "vitest";
|
|
10
|
+
import { getCSPNonce } from "@paypal/sdk-client/src";
|
|
11
|
+
|
|
12
|
+
import { prerenderTemplate } from "./prerender";
|
|
13
|
+
|
|
14
|
+
const TEST_NONCE = "prerender-nonce-abc123";
|
|
15
|
+
|
|
16
|
+
vi.mock("@paypal/sdk-client/src", () => ({
|
|
17
|
+
getCSPNonce: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
let capturedSpinnerPageProps = null;
|
|
21
|
+
let capturedChildren = null;
|
|
22
|
+
|
|
23
|
+
vi.mock("@paypal/common-components/src", () => ({
|
|
24
|
+
SpinnerPage: vi.fn().mockImplementation((props, children) => {
|
|
25
|
+
capturedSpinnerPageProps = props;
|
|
26
|
+
capturedChildren = children;
|
|
27
|
+
return {
|
|
28
|
+
render: vi.fn(() => document.createElement("div")),
|
|
29
|
+
};
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const originalWebValue = global.__WEB__;
|
|
34
|
+
|
|
35
|
+
const setupTest = () => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
capturedSpinnerPageProps = null;
|
|
38
|
+
capturedChildren = null;
|
|
39
|
+
global.__WEB__ = true;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const teardownTest = () => {
|
|
43
|
+
global.__WEB__ = originalWebValue;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe("prerenderTemplate", () => {
|
|
47
|
+
beforeEach(setupTest);
|
|
48
|
+
afterEach(teardownTest);
|
|
49
|
+
|
|
50
|
+
test.each([
|
|
51
|
+
[true, TEST_NONCE],
|
|
52
|
+
[false, undefined],
|
|
53
|
+
])(
|
|
54
|
+
"when __WEB__ is %s, should pass nonce %s to SpinnerPage",
|
|
55
|
+
(webValue, expectedNonce) => {
|
|
56
|
+
global.__WEB__ = webValue;
|
|
57
|
+
// $FlowIssue - mock return value
|
|
58
|
+
getCSPNonce.mockReturnValue(TEST_NONCE);
|
|
59
|
+
|
|
60
|
+
// $FlowIssue - test mock parameters
|
|
61
|
+
const result = prerenderTemplate({
|
|
62
|
+
doc: document,
|
|
63
|
+
close: vi.fn(),
|
|
64
|
+
// $FlowIssue - test mock parameters
|
|
65
|
+
props: {},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (webValue) {
|
|
69
|
+
// Verify getCSPNonce is called in web environment
|
|
70
|
+
expect(getCSPNonce).toHaveBeenCalled();
|
|
71
|
+
expect(getCSPNonce).toHaveReturnedWith(TEST_NONCE);
|
|
72
|
+
} else {
|
|
73
|
+
// Verify getCSPNonce is NOT called in non-web environment
|
|
74
|
+
expect(getCSPNonce).not.toHaveBeenCalled();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Verify SpinnerPage receives correct nonce prop
|
|
78
|
+
expect(result).toBeDefined();
|
|
79
|
+
expect(capturedSpinnerPageProps).toEqual({ nonce: expectedNonce });
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
test("should apply cspNonce to style element in children", () => {
|
|
84
|
+
// $FlowIssue - mock return value
|
|
85
|
+
getCSPNonce.mockReturnValue(TEST_NONCE);
|
|
86
|
+
|
|
87
|
+
// $FlowIssue - test mock parameters
|
|
88
|
+
prerenderTemplate({ doc: document, close: vi.fn(), props: {} });
|
|
89
|
+
|
|
90
|
+
expect(capturedChildren?.length).toBe(2);
|
|
91
|
+
// $FlowFixMe - capturedChildren is set by mock
|
|
92
|
+
expect(capturedChildren[0]?.props?.nonce).toBe(TEST_NONCE);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("should wire up close handler to close button", () => {
|
|
96
|
+
const mockClose = vi.fn();
|
|
97
|
+
// $FlowIssue - test mock parameters
|
|
98
|
+
prerenderTemplate({ doc: document, close: mockClose, props: {} });
|
|
99
|
+
|
|
100
|
+
// Verify second child is close button with correct props
|
|
101
|
+
expect(capturedChildren?.length).toBe(2);
|
|
102
|
+
// $FlowFixMe - capturedChildren is set by mock
|
|
103
|
+
const closeButton = capturedChildren[1];
|
|
104
|
+
expect(closeButton?.props?.onClick).toBe(mockClose);
|
|
105
|
+
expect(closeButton?.props?.id).toBe("close");
|
|
106
|
+
expect(closeButton?.props["aria-label"]).toBe("close");
|
|
107
|
+
});
|
|
108
|
+
});
|