@@ -2,14 +2,31 @@ import {
22 IDataOutMetadata ,
33 IDataOutMetadatum ,
44 IExecutionStep ,
5+ IGlobalVariable ,
56 IJSONObject ,
67} from '@plumber/types'
78
8- import { beforeEach , describe , expect , it } from 'vitest'
9+ import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
910
1011import { ADDRESS_LABELS } from '../../common/constants'
1112import trigger from '../../triggers/new-submission'
1213
14+ const pushTriggerItemMock = vi . fn ( )
15+ const getLastExecutionStepMock = vi . fn ( )
16+
17+ const mocks = vi . hoisted ( ( ) => ( {
18+ getMockData : vi . fn ( ) ,
19+ getFormDetailsFromGlobalVariable : vi . fn ( ) ,
20+ } ) )
21+
22+ vi . mock ( '../../triggers/new-submission/get-mock-data' , ( ) => ( {
23+ default : mocks . getMockData ,
24+ } ) )
25+
26+ vi . mock ( '../../common/webhook-settings' , ( ) => ( {
27+ getFormDetailsFromGlobalVariable : mocks . getFormDetailsFromGlobalVariable ,
28+ } ) )
29+
1330describe ( 'new submission trigger' , ( ) => {
1431 let executionStep : IExecutionStep
1532
@@ -26,6 +43,11 @@ describe('new submission trigger', () => {
2643 attr : 'name' ,
2744 } ,
2845 } ,
46+ headerFieldId : {
47+ question : 'Section Header' ,
48+ fieldType : 'section' ,
49+ order : 2 ,
50+ } ,
2951 } ,
3052 verifiedSubmitterInfo : {
3153 uinFin : 'S1234567B' ,
@@ -37,6 +59,158 @@ describe('new submission trigger', () => {
3759 } as unknown as IExecutionStep
3860 } )
3961
62+ describe ( 'testRun' , ( ) => {
63+ const $ = {
64+ auth : { data : { formId : '123' } } ,
65+ user :
{ email :
'[email protected] ' } , 66+ pushTriggerItem : pushTriggerItemMock ,
67+ getLastExecutionStep : getLastExecutionStepMock ,
68+ } as unknown as IGlobalVariable
69+
70+ const mockData = {
71+ formId : '123' ,
72+ responses : {
73+ mockTextFieldId : {
74+ question : 'What is your name?' ,
75+ answer : 'herp derp' ,
76+ } ,
77+ } ,
78+ }
79+
80+ const actualData = {
81+ formId : '123' ,
82+ submissionTime : '2025-06-17T14:25:14.195+08:00' ,
83+ responses : {
84+ textFieldId : {
85+ question : 'What is your age?' ,
86+ answer : 10 ,
87+ } ,
88+ } ,
89+ }
90+
91+ beforeEach ( ( ) => {
92+ mocks . getMockData . mockResolvedValue ( mockData )
93+ mocks . getFormDetailsFromGlobalVariable . mockReturnValue ( {
94+ formId : '123' ,
95+ } )
96+ } )
97+
98+ afterEach ( ( ) => {
99+ vi . clearAllMocks ( )
100+ } )
101+
102+ it ( 'should use mock data if preferMock is true and there is no past submission' , async ( ) => {
103+ getLastExecutionStepMock . mockResolvedValue ( null )
104+ await trigger . testRun ( $ , { preferMock : true } )
105+ expect ( pushTriggerItemMock ) . toHaveBeenCalledWith ( {
106+ raw : mockData ,
107+ meta : {
108+ internalId : '' ,
109+ isMock : true ,
110+ lastTestSubmissionDate : undefined ,
111+ } ,
112+ } )
113+ } )
114+
115+ it ( 'should use mock data if preferMock is true even though there is past submission' , async ( ) => {
116+ getLastExecutionStepMock . mockResolvedValue ( {
117+ dataOut : actualData ,
118+ createdAt : '2025-06-16 07:06:30.155+00' ,
119+ } )
120+ await trigger . testRun ( $ , { preferMock : true } )
121+ expect ( pushTriggerItemMock ) . toHaveBeenCalledWith ( {
122+ raw : mockData ,
123+ meta : {
124+ internalId : '' ,
125+ isMock : true ,
126+ lastTestSubmissionDate : new Date (
127+ '2025-06-17T14:25:14.195+08:00' ,
128+ ) . toISOString ( ) ,
129+ } ,
130+ } )
131+ } )
132+
133+ it ( 'should use mock data if testRunMetadata is undefined and there is no past submission' , async ( ) => {
134+ getLastExecutionStepMock . mockResolvedValue ( null )
135+ await trigger . testRun ( $ , undefined )
136+ expect ( pushTriggerItemMock ) . toHaveBeenCalledWith ( {
137+ raw : mockData ,
138+ meta : {
139+ internalId : '' ,
140+ isMock : true ,
141+ lastTestSubmissionDate : undefined ,
142+ } ,
143+ } )
144+ } )
145+
146+ it ( 'should use last test submission if testRunMetadata is undefined and there is past submission' , async ( ) => {
147+ getLastExecutionStepMock . mockResolvedValue ( {
148+ dataOut : actualData ,
149+ createdAt : '2025-06-16 07:06:30.155+00' ,
150+ } )
151+ await trigger . testRun ( $ , undefined )
152+ expect ( pushTriggerItemMock ) . toHaveBeenCalledWith ( {
153+ raw : actualData ,
154+ meta : {
155+ internalId : '' ,
156+ isMock : false ,
157+ lastTestSubmissionDate : new Date (
158+ '2025-06-17T14:25:14.195+08:00' ,
159+ ) . toISOString ( ) ,
160+ } ,
161+ } )
162+ } )
163+
164+ it ( 'should use last test submission if preferMock is false and there is past submission' , async ( ) => {
165+ getLastExecutionStepMock . mockResolvedValue ( {
166+ dataOut : actualData ,
167+ createdAt : '2025-06-16 07:06:30.155+00' ,
168+ } )
169+ await trigger . testRun ( $ , { preferMock : false } )
170+ expect ( pushTriggerItemMock ) . toHaveBeenCalledWith ( {
171+ raw : actualData ,
172+ meta : {
173+ internalId : '' ,
174+ isMock : false ,
175+ lastTestSubmissionDate : new Date (
176+ '2025-06-17T14:25:14.195+08:00' ,
177+ ) . toISOString ( ) ,
178+ } ,
179+ } )
180+ } )
181+
182+ it ( 'should use mock data if preferMock is false and there is no past submission' , async ( ) => {
183+ getLastExecutionStepMock . mockResolvedValue ( null )
184+ await trigger . testRun ( $ , { preferMock : false } )
185+ expect ( pushTriggerItemMock ) . toHaveBeenCalledWith ( {
186+ raw : mockData ,
187+ meta : {
188+ internalId : '' ,
189+ isMock : true ,
190+ lastTestSubmissionDate : undefined ,
191+ } ,
192+ } )
193+ } )
194+
195+ it ( 'should store the createdAt time if submissionTime is not available' , async ( ) => {
196+ getLastExecutionStepMock . mockResolvedValue ( {
197+ dataOut : { ...actualData , submissionTime : undefined } ,
198+ createdAt : '2025-06-16 07:06:30.155+00' ,
199+ } )
200+ await trigger . testRun ( $ , { preferMock : false } )
201+
202+ expect ( pushTriggerItemMock ) . toHaveBeenCalledWith ( {
203+ raw : { ...actualData , submissionTime : undefined } ,
204+ meta : {
205+ internalId : '' ,
206+ isMock : false ,
207+ lastTestSubmissionDate : new Date (
208+ '2025-06-16 07:06:30.155+00' ,
209+ ) . toISOString ( ) ,
210+ } ,
211+ } )
212+ } )
213+ } )
40214 describe ( 'dataOut metadata' , ( ) => {
41215 it ( 'ensures that only question, answer and answerArray props are visible' , async ( ) => {
42216 const metadata = await trigger . getDataOutMetadata ( executionStep )
@@ -64,10 +238,12 @@ describe('new submission trigger', () => {
64238 expect ( metadata . fields . textFieldId . question . label ) . toEqual ( 'Question 1' )
65239 } )
66240
67- it ( 'changes the answer label to "Response #n "' , async ( ) => {
241+ it ( 'changes the answer label to "1. What is your name? "' , async ( ) => {
68242 const metadata = await trigger . getDataOutMetadata ( executionStep )
69243
70- expect ( metadata . fields . textFieldId . answer . label ) . toEqual ( 'Response 1' )
244+ expect ( metadata . fields . textFieldId . answer . label ) . toEqual (
245+ '1. What is your name?' ,
246+ )
71247 } )
72248
73249 it ( 'positions the answer after the question' , async ( ) => {
@@ -78,20 +254,29 @@ describe('new submission trigger', () => {
78254 )
79255 } )
80256
81- it ( 'sets label and order to null if question number is undefined ' , async ( ) => {
257+ it ( 'should computes order for questions and answers even if order is not provided ' , async ( ) => {
82258 const fields = executionStep . dataOut . fields as IJSONObject
83- fields . textFieldId = {
84- question : 'What is your name?' ,
85- answer : 'herp derp' ,
86- fieldType : 'textField' ,
259+ delete fields . textFieldId
260+ // generate a few fields
261+ for ( let i = 0 ; i < 10 ; i ++ ) {
262+ fields [ `textFieldId${ i + 1 } ` ] = {
263+ question : `What is your name? ${ i } ` ,
264+ answer : 'herp derp' ,
265+ fieldType : 'textField' ,
266+ order : i < 5 ? i + 1 : null ,
267+ }
87268 }
88269
89270 const metadata = await trigger . getDataOutMetadata ( executionStep )
90271
91- expect ( metadata . fields . textFieldId . question . order ) . toBeNull ( )
92- expect ( metadata . fields . textFieldId . answer . order ) . toBeNull ( )
93- expect ( metadata . fields . textFieldId . question . label ) . toBeNull ( )
94- expect ( metadata . fields . textFieldId . answer . label ) . toBeNull ( )
272+ expect ( metadata . fields . textFieldId1 . question . order ) . toBe ( 1 )
273+ expect ( metadata . fields . textFieldId1 . answer . order ) . toBe ( 1.1 )
274+ expect ( metadata . fields . textFieldId2 . question . order ) . toBe ( 2 )
275+ expect ( metadata . fields . textFieldId2 . answer . order ) . toBe ( 2.1 )
276+ expect ( metadata . fields . textFieldId5 . question . order ) . toBe ( 5 )
277+ expect ( metadata . fields . textFieldId5 . answer . order ) . toBe ( 5.1 )
278+ expect ( metadata . fields . textFieldId6 . question . order ) . toBe ( 6 )
279+ expect ( metadata . fields . textFieldId6 . answer . order ) . toBe ( 6.1 )
95280 } )
96281
97282 it ( 'sets a label for SingPass verified NRIC/FIN' , async ( ) => {
@@ -170,6 +355,20 @@ describe('new submission trigger', () => {
170355 expect ( metadata . fields . fileFieldId . answer . label ) . toEqual ( 'Attach a file.' )
171356 } )
172357
358+ it ( 'collapses header fields' , async ( ) => {
359+ const metadata = await trigger . getDataOutMetadata ( executionStep )
360+ expect (
361+ metadata . fields . headerFieldId . question . isCollapsedByDefault ,
362+ ) . toEqual ( true )
363+ } )
364+
365+ it ( 'collapses question variables' , async ( ) => {
366+ const metadata = await trigger . getDataOutMetadata ( executionStep )
367+ expect ( metadata . fields . textFieldId . question . isCollapsedByDefault ) . toEqual (
368+ true ,
369+ )
370+ } )
371+
173372 it ( 'hides attachment questions' , async ( ) => {
174373 executionStep . dataOut . fields = {
175374 fileFieldId : {
@@ -277,7 +476,8 @@ describe('new submission trigger for answer array fields', () => {
277476 answerArray : [ 'lunch' , 'dinner' ] ,
278477 } ,
279478 textFieldId2 : {
280- question : 'What are your hobbies? When do you do them?' ,
479+ question :
480+ 'What are your hobbies? When do you do them? (activity, time)' ,
281481 fieldType : 'table' ,
282482 order : 2 ,
283483 answerArray : [
@@ -327,7 +527,7 @@ describe('new submission trigger for answer array fields', () => {
327527 // type will be array instead of text!
328528 expect ( array ) . toEqual ( {
329529 type : 'array' ,
330- label : 'Response 1 ' ,
530+ label : '1. Have you had your meals? ' ,
331531 order : 1.1 ,
332532 } )
333533 } )
@@ -345,8 +545,10 @@ describe('new submission trigger for answer array fields', () => {
345545 for ( let i = 0 ; i < array . length ; i ++ ) {
346546 const nestedArray = array [ i ]
347547 for ( let j = 0 ; j < array . length ; j ++ ) {
348- expect ( nestedArray [ j ] . label ) . toEqual (
349- `Response 2, Row ${ i + 1 } Column ${ j + 1 } ` ,
548+ expect ( nestedArray [ j ] . label ) . toBe (
549+ `2. Row ${ i + 1 } ${
550+ j === 0 ? 'activity' : 'time'
551+ } - What are your hobbies? When do you do them?`,
350552 )
351553 }
352554 }
@@ -370,7 +572,9 @@ describe('new submission trigger for answer array fields', () => {
370572
371573 expect ( addressMetadata ) . toHaveLength ( 5 )
372574 ADDRESS_LABELS . forEach ( ( label , index ) => {
373- expect ( addressMetadata [ index ] . label ) . toEqual ( `Response 4, ${ label } ` )
575+ expect ( addressMetadata [ index ] . label ) . toEqual (
576+ `4.${ index + 1 } . ${ label } - What is your address?` ,
577+ )
374578 } )
375579 } )
376580
@@ -379,7 +583,9 @@ describe('new submission trigger for answer array fields', () => {
379583 const addressMetadata = metadata . fields . addressFieldPartial
380584 . answerArray as IDataOutMetadatum [ ]
381585 ADDRESS_LABELS . forEach ( ( label , index ) => {
382- expect ( addressMetadata [ index ] . label ) . toEqual ( `Response 5, ${ label } ` )
586+ expect ( addressMetadata [ index ] . label ) . toEqual (
587+ `5.${ index + 1 } . ${ label } - What is your address?` ,
588+ )
383589 } )
384590 } )
385591 } )
0 commit comments