Setting the file. One moment.
Donation Form Test · Wix Headless Templates · wix/skills · Skills Docs
ContentsBack to the top of the page donations/tests/donation-form.test.cjs
donations/tests/ donation-form.test.cjs
JavaScript · 137 lines · 8 KB
}
=
require
(
'node:path'
);
8 const { createRequire } = require ( 'node:module' );
9 const dependencies = createRequire ( join ( resolve (process.env. DONATIONS_TEST_PROJECT || process. cwd ()), 'package.json' ));
10 const ts = dependencies ( 'typescript' );
11 const React = dependencies ( 'react' );
12 const { renderToStaticMarkup } = dependencies ( 'react-dom/server' );
13 const { JSDOM } = dependencies ( 'jsdom' );
14
15 const campaign = { id: 'campaign-id' , options: {
16 currency: 'USD' , frequencies: [{ value: 'ONE_TIME' , label: 'One-time' }, { value: 'MONTH' , label: 'Monthly' }],
17 presets: [{ amount: 25 , label: '$25' , impact: 'Care' }, { amount: 50 , label: '$50' , impact: 'Supplies' }],
18 customAmount: { enabled: true , minLabel: '$5' , maxLabel: '$1,000' },
19 askCoverFee: true , commentsEnabled: true , commentMaxLength: 100 ,
20 } };
21 const state = overrides => ({
22 available: true , frequency: 'ONE_TIME' , presetAmount: 25 , customMode: false , customAmount: '' ,
23 coverFee: false , note: '' , messages: {}, showErrors: false , valid: true , submitting: false ,
24 buttonLabel: 'Donate $25' , selectPreset () {}, selectCustom () {}, setCustomAmount () {},
25 setFrequency () {}, setCoverFee () {}, setNote () {}, donate : async () => {}, ... overrides,
26 });
27
28 function loadComponent ( file , hook , value ) {
29 const compiled = ts. transpileModule ( readFileSync (file, 'utf8' ), { compilerOptions: {
30 target: ts.ScriptTarget. ES2022 , module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX,
31 } }).outputText;
32 const result = { exports: {} };
33 const requireStub = name => name. includes ( '/hooks/donations/' ) ? { [hook]: () => typeof value === 'function' ? value () : value } : dependencies (name);
34 new Function ( 'require' , 'module' , 'exports' , compiled)(requireStub, result, result.exports);
35 return result.exports.default;
36 }
37 function render ( file , value , copies = 1 ) {
38 const Form = loadComponent (file, 'useDonation' , value);
39 const html = renderToStaticMarkup (React. createElement ( 'div' , null ,
40 ... Array. from ({ length: copies }, ( _ , key ) => React. createElement (Form, { campaign, key }, 'Campaign closed' ))));
41 return new JSDOM (html).window.document;
42 }
43
44 for ( const [ name , path ] of [[ 'source' , '../app/components/donations/' ], [ 'project' , '../project/src/components/donations/' ]]) {
45 const file = resolve (__dirname, path, 'DonateForm.tsx' );
46
47 test ( `${ name }: choices are labelled native radio groups with separate frequency and amount names` , () => {
48 const document = render (file, state ());
49 const groups = [ ... document. querySelectorAll ( 'fieldset' )];
50 assert. deepEqual (groups. map ( group => group. querySelector ( 'legend' ).textContent), [ 'How often' , 'Amount' ]);
51 const frequency = [ ... groups[ 0 ]. querySelectorAll ( 'input[type=radio]' )];
52 const amounts = [ ... groups[ 1 ]. querySelectorAll ( 'input[type=radio]' )];
53 assert. equal (frequency. length , 2 ); assert. equal (amounts. length , 3 );
54 assert. ok ([ ... frequency, ... amounts]. every ( input => input.labels. length === 1 ));
55 assert. equal ( new Set (frequency. map ( input => input.name)).size, 1 );
56 assert. equal ( new Set (amounts. map ( input => input.name)).size, 1 );
57 assert. notEqual (frequency[ 0 ].name, amounts[ 0 ].name);
58 frequency[ 1 ]. click ();
59 assert. equal (frequency[ 1 ].checked, true ); assert. equal (frequency[ 0 ].checked, false );
60 assert. equal (amounts[ 0 ].checked, true );
61 });
62
63 test ( `${ name }: two forms for the same campaign do not share ids or radio-group names` , () => {
64 const document = render (file, state ({ customMode: true }), 2 );
65 const ids = [ ... document. querySelectorAll ( '[id]' )]. map ( el => el.id);
66 assert. equal ( new Set (ids).size, ids. length );
67 const forms = document. querySelectorAll ( 'form' );
68 assert. notEqual (forms[ 0 ]. querySelector ( 'input[type=radio]' ).name, forms[ 1 ]. querySelector ( 'input[type=radio]' ).name);
69 });
70
71 test ( `${ name }: invalid custom amount and note reference their actual error text` , () => {
72 const document = render (file, state ({ customMode: true , showErrors: true , valid: false ,
73 messages: { customAmount: 'Enter an amount.' , note: 'Keep the note short.' } }));
74 const amount = document. querySelector ( 'input[inputmode=decimal]' );
75 assert. equal (amount. getAttribute ( 'aria-invalid' ), 'true' );
76 assert. equal (document. getElementById (amount. getAttribute ( 'aria-describedby' )).textContent, 'Enter an amount.' );
77 const note = document. querySelector ( 'textarea' );
78 assert. equal (note. getAttribute ( 'aria-invalid' ), 'true' );
79 assert. ok (note. getAttribute ( 'aria-describedby' ). split ( ' ' ). every ( id => document. getElementById (id)));
80 });
81
82 test ( `${ name }: pending submission disables choices, fields and repeated submits` , () => {
83 const document = render (file, state ({ customMode: true , submitting: true }));
84 assert. ok ([ ... document. querySelectorAll ( 'input,textarea,button' )]. every ( el => el. matches ( ':disabled' )));
85 assert. equal (document. querySelector ( 'button[type=submit]' ). getAttribute ( 'aria-busy' ), 'true' );
86 });
87
88 test ( `${ name }: invalid submit focuses the field after rendering, including a repeated attempt` , async () => {
89 const dom = new JSDOM ( '<div id="root"></div>' , { pretendToBeVisual: true });
90 const globals = { window: dom.window, document: dom.window.document, navigator: dom.window.navigator,
91 requestAnimationFrame: dom.window.requestAnimationFrame. bind (dom.window), IS_REACT_ACT_ENVIRONMENT: true };
92 const previous = new Map (Object. keys (globals). map ( key => [key, Object. getOwnPropertyDescriptor (globalThis, key)]));
93 for ( const [ key , value ] of Object. entries (globals)) Object. defineProperty (globalThis, key, { configurable: true , writable: true , value });
94 const { createRoot } = dependencies ( 'react-dom/client' );
95 const act = React.act || dependencies ( 'react-dom/test-utils' ).act;
96 const root = createRoot (dom.window.document. getElementById ( 'root' ));
97 const Form = loadComponent (file, 'useDonation' , () => {
98 const [ showErrors , setShowErrors ] = React. useState ( false );
99 return state ({ customMode: true , valid: false , showErrors, messages: { customAmount: 'Enter an amount.' },
100 donate : async () => setShowErrors ( true ) });
101 });
102 try {
103 await act ( async () => root. render (React. createElement (Form, { campaign })));
104 for ( let attempt = 0 ; attempt < 2 ; attempt ++ ) {
105 dom.window.document. querySelector ( 'button[type=submit]' ). focus ();
106 await act ( async () => dom.window.document. querySelector ( 'form' ). dispatchEvent ( new dom.window. Event ( 'submit' , { bubbles: true , cancelable: true })));
107 await new Promise ( resolve => dom.window. requestAnimationFrame (resolve));
108 assert. equal (dom.window.document.activeElement, dom.window.document. querySelector ( 'input[inputmode=decimal]' ));
109 }
110 } finally {
111 await act ( async () => root. unmount ());
112 dom.window. close ();
113 for ( const [ key , descriptor ] of previous) {
114 if (descriptor) Object. defineProperty (globalThis, key, descriptor);
115 else delete globalThis[key];
116 }
117 }
118 });
119
120 test ( `${ name }: a closed campaign has no donation controls` , () => {
121 const document = render (file, state ({ available: false }));
122 assert. equal (document. querySelector ( 'form' ), null );
123 assert. match (document.body.textContent, /Campaign closed/ );
124 });
125
126 test ( `${ name }: direct or unreadable returns have a heading and next step without submission claims` , () => {
127 const receiptFile = resolve (__dirname, path, 'DonationReceipt.tsx' );
128 for ( const orderId of [ '' , 'unverified-order' ]) {
129 const Receipt = loadComponent (receiptFile, 'useDonationReceipt' , { orderId, loading: false , receipt: null });
130 const document = new JSDOM ( renderToStaticMarkup (React. createElement (Receipt))).window.document;
131 assert. ok (document. querySelector ( 'h1' ));
132 assert. ok (document. querySelector ( 'a[href="/donate"]' ));
133 assert. doesNotMatch (document.body.textContent, /has been received | has been submitted | Thank you for your donation/ );
134 assert. match (document.body.textContent, /not a payment confirmation | could not be verified/ );
135 }
136 });
137 }