mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-01 18:13:03 +08:00
* fix(chat): prevent chat interface crash when AskUserQuestion payload is malformed Loading a session that contains an AskUserQuestion tool call could crash the entire chat interface with "TypeError: e.map is not a function". The AskUserQuestion tool is configured with `defaultOpen: true`, so QuestionAnswerContent renders as soon as the session loads. Its array guard (`!questions || questions.length === 0`) only checked for truthiness, and `q.options` was mapped/iterated with no guard at all. When `questions` or `options` arrive from the session transcript as a non-array value, the `.map()` / `.some()` calls throw and take down the whole chat view via the error boundary. Guard both with `Array.isArray()` so a single malformed message can no longer crash the interface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(chat): cover QuestionAnswerContent against malformed AskUserQuestion payloads Adds the first frontend regression test, guarding the crash fixed in the previous commit: a non-array `questions` value or a question missing its `options` array must render gracefully instead of throwing "e.map is not a function" and taking down the whole chat interface. Follows the repo's existing test convention (node:test + tsx); uses react-dom/server renderToStaticMarkup so no DOM/jsdom is required. Run with: npx tsx --test src/**/QuestionAnswerContent.test.tsx Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(chat): harden QuestionAnswerContent against malformed question entries Addresses review feedback: even with the array guards, a malformed transcript could still crash before the options fallback ran — - a `questions` entry that is null/non-object threw on `q.question` access - a non-string `answers[q.question]` threw on `answer.split(', ')` Skip entries that aren't a proper question object with a string prompt, and only call string methods on the answer when it is actually a string. Extends the regression test to cover both vectors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(chat): guard malformed question options --------- Co-authored-by: hustuhao <hustuhao@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Simos Mikelatos <simosmik@gmail.com>
78 lines
2.6 KiB
TypeScript
78 lines
2.6 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import React from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
import { QuestionAnswerContent } from './QuestionAnswerContent';
|
|
|
|
// Regression coverage for the chat-interface crash where an AskUserQuestion
|
|
// payload loaded from a session transcript arrives with a non-array `questions`
|
|
// or a question missing its `options` array. Rendering must degrade gracefully
|
|
// instead of throwing "TypeError: e.map is not a function".
|
|
|
|
test('renders without throwing when questions is a non-array value', () => {
|
|
assert.doesNotThrow(() => {
|
|
renderToStaticMarkup(
|
|
React.createElement(QuestionAnswerContent, {
|
|
// Malformed: object instead of an array
|
|
questions: { 0: { question: 'q?', options: [{ label: 'a' }] } } as never,
|
|
answers: {},
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
test('renders without throwing when a question is missing options[]', () => {
|
|
assert.doesNotThrow(() => {
|
|
renderToStaticMarkup(
|
|
React.createElement(QuestionAnswerContent, {
|
|
questions: [{ question: 'Pick one?', header: 'H' } as never],
|
|
answers: { 'Pick one?': 'X' },
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
test('renders without throwing when options[] contains malformed entries', () => {
|
|
assert.doesNotThrow(() => {
|
|
renderToStaticMarkup(
|
|
React.createElement(QuestionAnswerContent, {
|
|
questions: [{ question: 'Pick one?', options: [null, 'oops', { label: 'A' }] } as never],
|
|
answers: { 'Pick one?': 'A, Custom' },
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
test('renders without throwing when a questions entry is null/non-object', () => {
|
|
assert.doesNotThrow(() => {
|
|
renderToStaticMarkup(
|
|
React.createElement(QuestionAnswerContent, {
|
|
questions: [null, 'oops', { question: 'Ok?', options: [{ label: 'A' }] }] as never,
|
|
answers: {},
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
test('renders without throwing when an answer is a non-string value', () => {
|
|
assert.doesNotThrow(() => {
|
|
renderToStaticMarkup(
|
|
React.createElement(QuestionAnswerContent, {
|
|
questions: [{ question: 'Pick one?', options: [{ label: 'A' }] }],
|
|
// Malformed: answer is an object instead of the expected string
|
|
answers: { 'Pick one?': { unexpected: true } } as never,
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
test('still renders a well-formed question + answer', () => {
|
|
const html = renderToStaticMarkup(
|
|
React.createElement(QuestionAnswerContent, {
|
|
questions: [{ question: 'Pick one?', header: 'H', options: [{ label: 'A' }, { label: 'B' }] }],
|
|
answers: { 'Pick one?': 'A' },
|
|
}),
|
|
);
|
|
assert.ok(html.includes('Pick one?'));
|
|
});
|