Skip to content

Commit 76b5028

Browse files
committed
added tests
1 parent e39a665 commit 76b5028

File tree

2 files changed

+432
-0
lines changed

2 files changed

+432
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import type { SubBlockConfig } from '@/blocks/types'
6+
7+
const isFieldRequired = (config: SubBlockConfig, subBlockValues?: Record<string, any>): boolean => {
8+
if (!config.required) return false
9+
if (typeof config.required === 'boolean') return config.required
10+
11+
const evalCond = (
12+
cond: {
13+
field: string
14+
value: string | number | boolean | Array<string | number | boolean>
15+
not?: boolean
16+
and?: {
17+
field: string
18+
value: string | number | boolean | Array<string | number | boolean> | undefined
19+
not?: boolean
20+
}
21+
},
22+
values: Record<string, any>
23+
): boolean => {
24+
const fieldValue = values[cond.field]?.value
25+
const condValue = cond.value
26+
27+
let match: boolean
28+
if (Array.isArray(condValue)) {
29+
match = condValue.includes(fieldValue)
30+
} else {
31+
match = fieldValue === condValue
32+
}
33+
34+
if (cond.not) match = !match
35+
36+
if (cond.and) {
37+
const andFieldValue = values[cond.and.field]?.value
38+
const andCondValue = cond.and.value
39+
let andMatch: boolean
40+
if (Array.isArray(andCondValue)) {
41+
andMatch = andCondValue.includes(andFieldValue)
42+
} else {
43+
andMatch = andFieldValue === andCondValue
44+
}
45+
if (cond.and.not) andMatch = !andMatch
46+
match = match && andMatch
47+
}
48+
49+
return match
50+
}
51+
52+
const condition = typeof config.required === 'function' ? config.required() : config.required
53+
return evalCond(condition, subBlockValues || {})
54+
}
55+
56+
describe('isFieldRequired', () => {
57+
describe('boolean required', () => {
58+
it.concurrent('returns false when required is not set', () => {
59+
const config = { id: 'test', type: 'short-input' } as SubBlockConfig
60+
expect(isFieldRequired(config, {})).toBe(false)
61+
})
62+
63+
it.concurrent('returns false when required is false', () => {
64+
const config = { id: 'test', type: 'short-input', required: false } as SubBlockConfig
65+
expect(isFieldRequired(config, {})).toBe(false)
66+
})
67+
68+
it.concurrent('returns true when required is true', () => {
69+
const config = { id: 'test', type: 'short-input', required: true } as SubBlockConfig
70+
expect(isFieldRequired(config, {})).toBe(true)
71+
})
72+
})
73+
74+
describe('conditional required - simple value matching', () => {
75+
it.concurrent('returns true when field value matches condition value', () => {
76+
const config = {
77+
id: 'test',
78+
type: 'short-input',
79+
required: { field: 'operation', value: 'create_booking' },
80+
} as SubBlockConfig
81+
const values = { operation: { value: 'create_booking' } }
82+
expect(isFieldRequired(config, values)).toBe(true)
83+
})
84+
85+
it.concurrent('returns false when field value does not match condition value', () => {
86+
const config = {
87+
id: 'test',
88+
type: 'short-input',
89+
required: { field: 'operation', value: 'create_booking' },
90+
} as SubBlockConfig
91+
const values = { operation: { value: 'cancel_booking' } }
92+
expect(isFieldRequired(config, values)).toBe(false)
93+
})
94+
95+
it.concurrent('returns false when field is missing', () => {
96+
const config = {
97+
id: 'test',
98+
type: 'short-input',
99+
required: { field: 'operation', value: 'create_booking' },
100+
} as SubBlockConfig
101+
expect(isFieldRequired(config, {})).toBe(false)
102+
})
103+
104+
it.concurrent('returns false when field value is undefined', () => {
105+
const config = {
106+
id: 'test',
107+
type: 'short-input',
108+
required: { field: 'operation', value: 'create_booking' },
109+
} as SubBlockConfig
110+
const values = { operation: { value: undefined } }
111+
expect(isFieldRequired(config, values)).toBe(false)
112+
})
113+
})
114+
115+
describe('conditional required - array value matching', () => {
116+
it.concurrent('returns true when field value is in condition array', () => {
117+
const config = {
118+
id: 'test',
119+
type: 'short-input',
120+
required: { field: 'operation', value: ['create_booking', 'update_booking'] },
121+
} as SubBlockConfig
122+
const values = { operation: { value: 'create_booking' } }
123+
expect(isFieldRequired(config, values)).toBe(true)
124+
})
125+
126+
it.concurrent('returns false when field value is not in condition array', () => {
127+
const config = {
128+
id: 'test',
129+
type: 'short-input',
130+
required: { field: 'operation', value: ['create_booking', 'update_booking'] },
131+
} as SubBlockConfig
132+
const values = { operation: { value: 'cancel_booking' } }
133+
expect(isFieldRequired(config, values)).toBe(false)
134+
})
135+
})
136+
137+
describe('conditional required - negation', () => {
138+
it.concurrent('returns false when field matches but not is true', () => {
139+
const config = {
140+
id: 'test',
141+
type: 'short-input',
142+
required: { field: 'operation', value: 'create_booking', not: true },
143+
} as SubBlockConfig
144+
const values = { operation: { value: 'create_booking' } }
145+
expect(isFieldRequired(config, values)).toBe(false)
146+
})
147+
148+
it.concurrent('returns true when field does not match and not is true', () => {
149+
const config = {
150+
id: 'test',
151+
type: 'short-input',
152+
required: { field: 'operation', value: 'create_booking', not: true },
153+
} as SubBlockConfig
154+
const values = { operation: { value: 'cancel_booking' } }
155+
expect(isFieldRequired(config, values)).toBe(true)
156+
})
157+
})
158+
159+
describe('conditional required - compound conditions', () => {
160+
it.concurrent('returns true when both conditions match', () => {
161+
const config = {
162+
id: 'test',
163+
type: 'short-input',
164+
required: {
165+
field: 'operation',
166+
value: 'create_booking',
167+
and: { field: 'hasEmail', value: true },
168+
},
169+
} as SubBlockConfig
170+
const values = {
171+
operation: { value: 'create_booking' },
172+
hasEmail: { value: true },
173+
}
174+
expect(isFieldRequired(config, values)).toBe(true)
175+
})
176+
177+
it.concurrent('returns false when first matches but and fails', () => {
178+
const config = {
179+
id: 'test',
180+
type: 'short-input',
181+
required: {
182+
field: 'operation',
183+
value: 'create_booking',
184+
and: { field: 'hasEmail', value: true },
185+
},
186+
} as SubBlockConfig
187+
const values = {
188+
operation: { value: 'create_booking' },
189+
hasEmail: { value: false },
190+
}
191+
expect(isFieldRequired(config, values)).toBe(false)
192+
})
193+
})
194+
})
195+
196+
describe('condition + required equivalence', () => {
197+
const conditionValue = { field: 'operation', value: 'calcom_create_booking' }
198+
199+
const configWithConditionalRequired = {
200+
id: 'attendeeName',
201+
type: 'short-input',
202+
condition: conditionValue,
203+
required: conditionValue,
204+
} as SubBlockConfig
205+
206+
const configWithSimpleRequired = {
207+
id: 'attendeeName',
208+
type: 'short-input',
209+
condition: conditionValue,
210+
required: true,
211+
} as SubBlockConfig
212+
213+
describe('when condition IS met (field is visible)', () => {
214+
const valuesWhenVisible = { operation: { value: 'calcom_create_booking' } }
215+
216+
it.concurrent('conditional required returns true', () => {
217+
expect(isFieldRequired(configWithConditionalRequired, valuesWhenVisible)).toBe(true)
218+
})
219+
220+
it.concurrent('simple required returns true', () => {
221+
expect(isFieldRequired(configWithSimpleRequired, valuesWhenVisible)).toBe(true)
222+
})
223+
224+
it.concurrent('both configs produce the same result', () => {
225+
const conditionalResult = isFieldRequired(configWithConditionalRequired, valuesWhenVisible)
226+
const simpleResult = isFieldRequired(configWithSimpleRequired, valuesWhenVisible)
227+
expect(conditionalResult).toBe(simpleResult)
228+
})
229+
})
230+
231+
describe('when condition is NOT met (field is hidden)', () => {
232+
const valuesWhenHidden = { operation: { value: 'calcom_cancel_booking' } }
233+
234+
it.concurrent('conditional required returns false', () => {
235+
expect(isFieldRequired(configWithConditionalRequired, valuesWhenHidden)).toBe(false)
236+
})
237+
238+
it.concurrent('simple required returns true but field is hidden', () => {
239+
expect(isFieldRequired(configWithSimpleRequired, valuesWhenHidden)).toBe(true)
240+
})
241+
242+
it.concurrent('results differ but field is hidden when condition fails', () => {
243+
const conditionalResult = isFieldRequired(configWithConditionalRequired, valuesWhenHidden)
244+
const simpleResult = isFieldRequired(configWithSimpleRequired, valuesWhenHidden)
245+
expect(conditionalResult).not.toBe(simpleResult)
246+
})
247+
})
248+
249+
describe('practical equivalence for user-facing behavior', () => {
250+
it.concurrent('when field is visible both show required indicator', () => {
251+
const valuesWhenVisible = { operation: { value: 'calcom_create_booking' } }
252+
const showsRequiredIndicatorA = isFieldRequired(
253+
configWithConditionalRequired,
254+
valuesWhenVisible
255+
)
256+
const showsRequiredIndicatorB = isFieldRequired(configWithSimpleRequired, valuesWhenVisible)
257+
expect(showsRequiredIndicatorA).toBe(true)
258+
expect(showsRequiredIndicatorB).toBe(true)
259+
})
260+
})
261+
})

0 commit comments

Comments
 (0)