Skip to content

Commit 59e2e48

Browse files
author
kappa90
committed
fix(ph-ai): fix custom answer form tool
1 parent 85512f8 commit 59e2e48

File tree

2 files changed

+285
-2
lines changed

2 files changed

+285
-2
lines changed
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
import { expectLogic } from 'kea-test-utils'
2+
3+
import { MultiQuestionForm } from '~/queries/schema/schema-assistant-messages'
4+
import { initKeaTests } from '~/test/init'
5+
6+
import { multiQuestionFormLogic } from './multiQuestionFormLogic'
7+
8+
const createMockForm = (questionCount: number): MultiQuestionForm => ({
9+
type: 'multi_question_form' as const,
10+
questions: Array.from({ length: questionCount }, (_, i) => ({
11+
id: `q${i + 1}`,
12+
question: `Question ${i + 1}?`,
13+
options: [{ value: 'Option A' }, { value: 'Option B' }],
14+
allow_custom_answer: true,
15+
})),
16+
})
17+
18+
describe('multiQuestionFormLogic', () => {
19+
let onSubmit: jest.Mock
20+
21+
beforeEach(() => {
22+
initKeaTests()
23+
onSubmit = jest.fn()
24+
})
25+
26+
describe('selectOption', () => {
27+
it('advances to next question when not on last question', async () => {
28+
const form = createMockForm(2)
29+
const logic = multiQuestionFormLogic({ form, onSubmit })
30+
logic.mount()
31+
32+
await expectLogic(logic, () => {
33+
logic.actions.selectOption('Option A')
34+
})
35+
.toDispatchActions(['selectOption', 'setAnswersValues', 'advanceToNextQuestion'])
36+
.toMatchValues({
37+
currentQuestionIndex: 1,
38+
answers: { q1: 'Option A' },
39+
})
40+
41+
expect(onSubmit).not.toHaveBeenCalled()
42+
})
43+
44+
it('submits form when on last question', async () => {
45+
const form = createMockForm(1)
46+
const logic = multiQuestionFormLogic({ form, onSubmit })
47+
logic.mount()
48+
49+
await expectLogic(logic, () => {
50+
logic.actions.selectOption('Option B')
51+
})
52+
.toDispatchActions(['selectOption', 'setAnswersValues', 'submitAnswers'])
53+
.toMatchValues({
54+
answers: { q1: 'Option B' },
55+
})
56+
57+
expect(onSubmit).toHaveBeenCalledWith({ q1: 'Option B' })
58+
})
59+
60+
it('clears custom input when option is selected', async () => {
61+
const form = createMockForm(2)
62+
const logic = multiQuestionFormLogic({ form, onSubmit })
63+
logic.mount()
64+
65+
logic.actions.setCustomInput('some text')
66+
logic.actions.setShowCustomInput(true)
67+
68+
await expectLogic(logic).toMatchValues({
69+
customInput: 'some text',
70+
showCustomInput: true,
71+
})
72+
73+
await expectLogic(logic, () => {
74+
logic.actions.selectOption('Option A')
75+
}).toMatchValues({
76+
customInput: '',
77+
showCustomInput: false,
78+
})
79+
})
80+
})
81+
82+
describe('submitCustomAnswer', () => {
83+
it('does nothing when custom input is empty', async () => {
84+
const form = createMockForm(1)
85+
const logic = multiQuestionFormLogic({ form, onSubmit })
86+
logic.mount()
87+
88+
logic.actions.setCustomInput('')
89+
90+
await expectLogic(logic, () => {
91+
logic.actions.submitCustomAnswer()
92+
})
93+
.toDispatchActions(['submitCustomAnswer'])
94+
.toNotHaveDispatchedActions(['setAnswersValues', 'submitAnswers'])
95+
96+
expect(onSubmit).not.toHaveBeenCalled()
97+
})
98+
99+
it('does nothing when custom input is only whitespace', async () => {
100+
const form = createMockForm(1)
101+
const logic = multiQuestionFormLogic({ form, onSubmit })
102+
logic.mount()
103+
104+
logic.actions.setCustomInput(' ')
105+
106+
await expectLogic(logic, () => {
107+
logic.actions.submitCustomAnswer()
108+
})
109+
.toDispatchActions(['submitCustomAnswer'])
110+
.toNotHaveDispatchedActions(['setAnswersValues', 'submitAnswers'])
111+
112+
expect(onSubmit).not.toHaveBeenCalled()
113+
})
114+
115+
it('submits custom answer and advances to next question when not on last question', async () => {
116+
const form = createMockForm(2)
117+
const logic = multiQuestionFormLogic({ form, onSubmit })
118+
logic.mount()
119+
120+
logic.actions.setCustomInput('My custom answer')
121+
logic.actions.setShowCustomInput(true)
122+
123+
await expectLogic(logic, () => {
124+
logic.actions.submitCustomAnswer()
125+
})
126+
.toDispatchActions([
127+
'submitCustomAnswer',
128+
'setAnswersValues',
129+
'setCustomInput',
130+
'setShowCustomInput',
131+
'advanceToNextQuestion',
132+
])
133+
.toMatchValues({
134+
currentQuestionIndex: 1,
135+
answers: { q1: 'My custom answer' },
136+
customInput: '',
137+
showCustomInput: false,
138+
})
139+
140+
expect(onSubmit).not.toHaveBeenCalled()
141+
})
142+
143+
it('submits custom answer and submits form when on last question', async () => {
144+
const form = createMockForm(1)
145+
const logic = multiQuestionFormLogic({ form, onSubmit })
146+
logic.mount()
147+
148+
logic.actions.setCustomInput('My custom answer')
149+
logic.actions.setShowCustomInput(true)
150+
151+
await expectLogic(logic, () => {
152+
logic.actions.submitCustomAnswer()
153+
})
154+
.toDispatchActions([
155+
'submitCustomAnswer',
156+
'setAnswersValues',
157+
'setCustomInput',
158+
'setShowCustomInput',
159+
'submitAnswers',
160+
])
161+
.toMatchValues({
162+
answers: { q1: 'My custom answer' },
163+
customInput: '',
164+
showCustomInput: false,
165+
})
166+
167+
expect(onSubmit).toHaveBeenCalledWith({ q1: 'My custom answer' })
168+
})
169+
170+
it('trims whitespace from custom answer', async () => {
171+
const form = createMockForm(1)
172+
const logic = multiQuestionFormLogic({ form, onSubmit })
173+
logic.mount()
174+
175+
logic.actions.setCustomInput(' trimmed answer ')
176+
177+
await expectLogic(logic, () => {
178+
logic.actions.submitCustomAnswer()
179+
}).toMatchValues({
180+
answers: { q1: 'trimmed answer' },
181+
})
182+
183+
expect(onSubmit).toHaveBeenCalledWith({ q1: 'trimmed answer' })
184+
})
185+
})
186+
187+
describe('multi-question flow', () => {
188+
it('handles mixed option and custom answers across questions', async () => {
189+
const form = createMockForm(3)
190+
const logic = multiQuestionFormLogic({ form, onSubmit })
191+
logic.mount()
192+
193+
// Answer first question with an option
194+
await expectLogic(logic, () => {
195+
logic.actions.selectOption('Option A')
196+
}).toMatchValues({
197+
currentQuestionIndex: 1,
198+
answers: { q1: 'Option A' },
199+
})
200+
201+
// Answer second question with custom input
202+
logic.actions.setCustomInput('Custom for Q2')
203+
await expectLogic(logic, () => {
204+
logic.actions.submitCustomAnswer()
205+
}).toMatchValues({
206+
currentQuestionIndex: 2,
207+
answers: { q1: 'Option A', q2: 'Custom for Q2' },
208+
})
209+
210+
// Answer third (last) question with an option - should submit
211+
await expectLogic(logic, () => {
212+
logic.actions.selectOption('Option B')
213+
}).toMatchValues({
214+
answers: { q1: 'Option A', q2: 'Custom for Q2', q3: 'Option B' },
215+
})
216+
217+
expect(onSubmit).toHaveBeenCalledWith({
218+
q1: 'Option A',
219+
q2: 'Custom for Q2',
220+
q3: 'Option B',
221+
})
222+
})
223+
224+
it('submits form with custom answer on last question', async () => {
225+
const form = createMockForm(2)
226+
const logic = multiQuestionFormLogic({ form, onSubmit })
227+
logic.mount()
228+
229+
// Answer first question
230+
logic.actions.selectOption('Option A')
231+
232+
// Answer last question with custom input
233+
logic.actions.setCustomInput('Final custom answer')
234+
await expectLogic(logic, () => {
235+
logic.actions.submitCustomAnswer()
236+
}).toMatchValues({
237+
answers: { q1: 'Option A', q2: 'Final custom answer' },
238+
})
239+
240+
expect(onSubmit).toHaveBeenCalledWith({
241+
q1: 'Option A',
242+
q2: 'Final custom answer',
243+
})
244+
})
245+
})
246+
247+
describe('selectors', () => {
248+
it('questionsCount returns correct count', async () => {
249+
const form = createMockForm(3)
250+
const logic = multiQuestionFormLogic({ form, onSubmit })
251+
logic.mount()
252+
253+
await expectLogic(logic).toMatchValues({
254+
questionsCount: 3,
255+
})
256+
})
257+
258+
it.each([
259+
[3, 0, false],
260+
[3, 1, false],
261+
[3, 2, true],
262+
[1, 0, true],
263+
])('isLastQuestion with %i questions at index %i is %s', async (questionCount, index, expected) => {
264+
const form = createMockForm(questionCount)
265+
const logic = multiQuestionFormLogic({ form, onSubmit })
266+
logic.mount()
267+
268+
// Advance to the target index
269+
for (let i = 0; i < index; i++) {
270+
logic.actions.selectOption('Option A')
271+
}
272+
273+
await expectLogic(logic).toMatchValues({
274+
currentQuestionIndex: index,
275+
isLastQuestion: expected,
276+
})
277+
})
278+
})
279+
})

frontend/src/scenes/max/messages/multiQuestionFormLogic.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ export const multiQuestionFormLogic = kea<multiQuestionFormLogicType>([
3636
{
3737
setCustomInput: (_: string, { value }) => value,
3838
selectOption: () => '',
39-
submitCustomAnswer: () => '',
39+
advanceToNextQuestion: () => '',
4040
},
4141
],
4242
showCustomInput: [
4343
false,
4444
{
4545
setShowCustomInput: (_: boolean, { show }) => show,
4646
selectOption: () => false,
47-
submitCustomAnswer: () => false,
47+
advanceToNextQuestion: () => false,
4848
},
4949
],
5050
isSubmitted: [
@@ -96,6 +96,10 @@ export const multiQuestionFormLogic = kea<multiQuestionFormLogicType>([
9696
const updatedAnswers = { ...values.answers, [currentQuestion.id]: trimmedValue }
9797
actions.setAnswersValues(updatedAnswers)
9898

99+
// Clear custom input and hide the input field after capturing the value
100+
actions.setCustomInput('')
101+
actions.setShowCustomInput(false)
102+
99103
if (values.isLastQuestion) {
100104
// Trigger form submission - kea-forms will call the submit handler with the current values
101105
actions.submitAnswers()

0 commit comments

Comments
 (0)