apps/web/src/lib/eventFacts.test.ts
1import { expect, test } from 'bun:test'
2import {
3 eventFactReply,
4 eventFactTopic,
5 publishedFactLinks,
6 recordingPolicyReply,
7 selectEventFactAnswers,
8 startupAwardReply,
9 startupFactReply,
10 startupPackageInclusions,
11} from './eventFacts'
12
13test('exact FAQs retain aliases, conditional policy, and event isolation', () => {
14 const docs = [
15 {
16 source: 'FAQ.txt',
17 text:
18 '[UNLEASH in Miami 2027]\nQ: Can I register on-site?\nA: Miami policy.\n\n[UNLEASH in Paris 2026]\nQ: Can I register on-site?\nQ: Is onsite registration available?\nA: On-site registration may be available if capacity allows.\n\n[UNLEASH in Paris 2026]\nQ: Can I get free tickets?\nA: Apply via the HR professionals programme; group tickets and sponsor packages are other routes.',
19 },
20 ]
21 const answers = selectEventFactAnswers(docs, 'paris', 'onsiteRegistration')
22 expect(answers).toHaveLength(1)
23 expect(eventFactReply('onsiteRegistration', answers)).toBe(
24 'On-site registration may be available if capacity allows.',
25 )
26 expect(
27 selectEventFactAnswers(docs, 'paris', 'freeTickets')[0].answer,
28 ).toContain('group tickets')
29})
30
31test('an early-bird answer does not append an unrelated group-discount answer', () => {
32 const answers = [
33 {
34 question: 'Is there an early bird discount?',
35 answer: 'Early booking offers may be available.',
36 source: 'FAQ',
37 },
38 {
39 question: 'Is there a group discount?',
40 answer: 'Group discounts may be available.',
41 source: 'FAQ',
42 },
43 ]
44 expect(
45 eventFactReply('discounts', answers, 'Is there an early bird discount?'),
46 ).toContain('Early booking')
47 expect(
48 eventFactReply('discounts', answers, 'Is there an early bird discount?'),
49 ).not.toContain('Group discounts')
50})
51
52test('startup eligibility excludes a sponsor-list FAQ and separates benefits from approval rules', () => {
53 const answers = selectEventFactAnswers(
54 [
55 {
56 source: 'Paris FAQ',
57 text: `[UNLEASH in Paris 2026]
58Q: Who sponsors the startup event?
59A: Visit Exhibition for sponsors.
60
61Q: Who are the sponsors and exhibitors?
62Q: Which companies, sponsors, and startups are exhibiting at the marketplace?
63A: Visit Exhibition to see key sponsors.
64
65Q: Can startups exhibit within the Startup Programme?
66A: Eligible HR technology startups can apply; the team reviews applications.`,
67 },
68 ],
69 'paris',
70 'startupEligibility',
71 )
72 expect(answers).toHaveLength(1)
73 expect(answers[0].answer).toContain('reviews applications')
74 expect(eventFactTopic('I have applied for Explorer. What next?')).toBe(
75 'explorerApplication',
76 )
77 expect(
78 eventFactTopic('I work for Workday. Can I get an HR Leader pass?'),
79 ).toBe('hrLeaderEligibility')
80 expect(eventFactTopic('We are pre-seed. Is there a lower-cost option?')).toBe(
81 'startupOptions',
82 )
83 expect(eventFactTopic('Can I bring my own stand builder?')).toBe(
84 'exhibitorStandPlans',
85 )
86})
87
88test('exhibitor guidance uses the manual and correct contact without a sales fallback', () => {
89 expect(eventFactReply('exhibitorStandPlans', [])).toContain('exhibitor manual')
90 expect(eventFactReply('exhibitorStandPlans', [])).toContain(
91 'customersuccess@unleash.ai',
92 )
93 expect(
94 eventFactReply('onsiteRegistration', [
95 {
96 question: 'Q',
97 source: 'FAQ',
98 answer:
99 'Please contact the Customer Success team at customersuccess@unleash.ai.',
100 },
101 ]),
102 ).toBe('Please contact the Support team at support@unleash.ai.')
103})
104
105test('package inclusions use the complete source block without inventing eligibility or omitting staff access', () => {
106 expect(
107 startupPackageInclusions({
108 content:
109 'What the Startup Networking Badge includes:\nFull conference access for 2x staff members\nAccess to all 7 stages\nBuilt around the decisions ahead\nOther content',
110 }),
111 ).toBe(
112 'The Startup Networking Badge includes:\n\n- Full conference access for 2x staff members\n- Access to all 7 stages',
113 )
114 expect(
115 startupPackageInclusions({ content: 'Applications are reviewed.' }),
116 ).toBeUndefined()
117 expect(
118 eventFactTopic('What does the Startup Networking Package include?'),
119 ).toBe('startupInclusions')
120 expect(
121 eventFactTopic('What would I get with it?', [
122 { role: 'user', text: 'I want the startup networking package' },
123 ]),
124 ).toBe('startupInclusions')
125 expect(
126 selectEventFactAnswers(
127 [
128 {
129 source: 'FAQ',
130 text:
131 '[Paris 2026 | audience: exhibitor_sponsor]\nQ: How do I book parking for exhibitors?\nA: Booking instructions will be in the exhibitor manual.',
132 },
133 ],
134 'paris',
135 'parking',
136 ),
137 ).toEqual([])
138})
139
140test('volunteer follow-up stays factual but a workflow confirmation is not a facts request', () => {
141 expect(
142 eventFactTopic('What kind of roles are there?', [
143 { role: 'user', text: 'Can I volunteer?' },
144 ]),
145 ).toBe('volunteering')
146 expect(eventFactTopic('Yes, please send it.')).toBeUndefined()
147 expect(
148 eventFactTopic('My role is HR Director', [
149 { role: 'user', text: 'Can I volunteer?' },
150 ]),
151 ).toBeUndefined()
152})
153
154test('FAQ answers never link unpublished internal pages', () => {
155 expect(
156 publishedFactLinks(
157 'See [Old manual](/events/unleash-paris/manual) or Attend (https://www.unleash.ai/events/unleash-paris/attend). Partner: https://partner.example/apply',
158 ['/events/unleash-paris/attend'],
159 ),
160 ).toBe(
161 'See Old manual or Attend (https://www.unleash.ai/events/unleash-paris/attend). Partner: https://partner.example/apply',
162 )
163 expect(
164 publishedFactLinks(
165 'Old https://staging-abc.unleash.ai/events/unleash-paris/manual',
166 [],
167 ),
168 ).toBe('Old ')
169})
170
171test('audience and recording answers need complete exact source evidence', () => {
172 const docs = [
173 {
174 source: 'Paris facts',
175 text: `[UNLEASH in Paris 2026]
176Q: How many people and speakers attend UNLEASH in Paris?
177A: 8500+ attendees and 300+ speakers.
178
179Q: What industries attend?
180A: HR, workforce technology and recruitment.`,
181 },
182 ]
183 expect(eventFactTopic('How many people attend?')).toBe('attendance')
184 expect(eventFactTopic('What industries attend?')).toBe('industries')
185 expect(
186 eventFactReply(
187 'attendance',
188 selectEventFactAnswers(docs, 'paris', 'attendance'),
189 ),
190 ).toContain('300+ speakers')
191 expect(selectEventFactAnswers(docs, 'paris', 'recordingAccess')).toEqual([])
192 expect(eventFactReply('recordingAccess', [])).toBeUndefined()
193 const policy = recordingPolicyReply([
194 {
195 title: 'Event terms',
196 url: '/events/unleash-paris/event-terms-and-conditions',
197 content:
198 'Show recordings purchased before, at or after the show can take up to 6-8 weeks to be available. Other policy.',
199 },
200 ])
201 expect(policy).toContain('can take up to 6-8 weeks')
202 expect(policy).toContain('does not establish that recordings are included')
203 expect(
204 recordingPolicyReply([
205 {
206 title: 'Media',
207 url: '/events/unleash-paris/attend/media',
208 content: 'Recordings are immediate.',
209 },
210 ]),
211 ).toBeUndefined()
212})
213
214test('ticket policy aliases resolve complete FAQs without confusing exhibitor registration or package upgrades', () => {
215 const documents = [
216 {
217 source: 'Paris FAQ',
218 text: `[UNLEASH in Paris 2026]
219Q: How do I book my place?
220A: Register online, complete checkout, and receive a confirmation email.
221
222Q: How do I register my exhibitor team?
223A: Use the exhibitor portal.
224
225Q: Can I upgrade my sponsorship package?
226A: Discuss sponsorship options.
227
228Q: Can I upgrade my ticket or move to a higher-tier pass?
229A: An upgrade may be possible if available. Contact the Customer Success team.
230
231Q: Can I transfer my ticket to a colleague?
232A: Transfers are usually possible subject to Terms & Conditions and deadlines.`,
233 },
234 ]
235 expect(eventFactTopic('Can a colleague go instead of me?')).toBe(
236 'ticketTransfers',
237 )
238 expect(eventFactTopic('Can I move to a higher tier pass later?')).toBe(
239 'ticketUpgrades',
240 )
241 expect(
242 selectEventFactAnswers(documents, 'paris', 'registration'),
243 ).toHaveLength(1)
244 const upgrades = selectEventFactAnswers(documents, 'paris', 'ticketUpgrades')
245 expect(upgrades).toHaveLength(1)
246 expect(eventFactReply('ticketUpgrades', upgrades)).toContain(
247 'support@unleash.ai',
248 )
249 expect(eventFactReply('ticketUpgrades', upgrades)).not.toContain(
250 'sponsorship options',
251 )
252 expect(
253 selectEventFactAnswers(documents, 'miami', 'ticketTransfers'),
254 ).toHaveLength(0)
255})
256
257test('startup follow-ups answer pricing, audience and registration separately without repeating benefits', () => {
258 const page = {
259 title: 'Startup Networking Package',
260 url: '/events/unleash-paris/exhibition/startup-networking-package',
261 content:
262 'What the Startup Networking Badge includes:\nFull conference access for 2x staff members\nAccess to all 7 stages\n\nBuilt around your needs.\n[Get Your Badge](<https://events.unleash.ai/startup>)',
263 }
264 const history = [
265 {
266 role: 'assistant',
267 text:
268 'The Startup Networking Package is for HR technology startups and scaleups.',
269 },
270 ]
271 const price = startupFactReply(
272 'startupOptions',
273 page,
274 'Does the startup package pricing apply to companies our size?',
275 history,
276 )
277 expect(price).toContain('can’t confirm')
278 expect(price).not.toContain('EUR')
279 expect(price).not.toContain('Full conference access')
280 expect(price).toContain('https://events.unleash.ai/startup')
281 const apply = startupFactReply(
282 'startupOptions',
283 page,
284 'Should I register or apply for approval first?',
285 history,
286 )
287 expect(apply).toContain('separate option')
288 expect(apply).toContain('applying to exhibit')
289 const criteria = startupFactReply(
290 'startupEligibility',
291 page,
292 'We are recruitment tech. Do we qualify?',
293 history,
294 )
295 expect(criteria).toContain('Your description is relevant')
296 expect(criteria).toContain('guarantee approval')
297 expect(criteria).not.toContain('Full conference access')
298 expect(eventFactTopic('What criteria are used?', history)).toBe(
299 'startupEligibility',
300 )
301 expect(eventFactTopic('What is the startup package pricing?', history)).toBe(
302 'startupOptions',
303 )
304 expect(eventFactTopic('How much is Diamond?', history)).toBeUndefined()
305})
306
307test('Award questions use the published competition and its application rather than badge purchase', () => {
308 const page = {
309 title: 'Startup Award',
310 url: '/events/unleash-paris/exhibition/startup-award',
311 content:
312 '[Apply Now](<https://events.unleash.ai/award>)\nThe\nUNLEASH Startup Award is the longest-running startup competition\nfor HR Tech.\nPitch in front of industry experts for a potential support package.\nUnlock the future of HR',
313 }
314 expect(
315 eventFactTopic('What does the Startup Award involve and how do I apply?'),
316 ).toBe('startupAward')
317 const answer = startupAwardReply(page)
318 expect(answer).toContain('Pitch in front of industry experts')
319 expect(answer).toContain('https://events.unleash.ai/award')
320 expect(answer).toContain('does not guarantee selection or a prize')
321 expect(answer).not.toContain('registration-startup-pass')
322})
323