문제와 원인
모의고사 시작 응답은 제출 전 정답·해설 필드를 생략한다. 학습 화면은 explanation을 문자열로 가정해 해설 개수를 계산했고, 시험 문항을 상태에 병합한 뒤 trim()에서 TypeError가 발생했다.
수정 전 코드
StudyApp
() => data.questions.filter((question) => question.explanation.trim()).length
requestStudyMutation
읽기 응답에는 문항 정규화가 있었지만 저장 응답의 requestStudyMutation은 결과를 그대로 반환했다.
export async function requestStudyMutation<T extends object>(
action: StudyMutationAction,
payload: JsonRecord = {},
signal?: AbortSignal,
) {
return requestMutation(STUDY_MUTATION_ADAPTER, action, payload, signal) as Promise<T>;
}
수정 후 코드
requestStudyMutation
exam-start 응답에 withSafeStudyQuestionDefaults를 적용해 없는 해설은 "", 없는 배열은 []로 채웠다. 제출 응답은 서버의 정답·해설을 그대로 반환한다.
export async function requestStudyMutation<T extends object>(
action: StudyMutationAction,
payload: JsonRecord = {},
signal?: AbortSignal,
) {
const result = await requestMutation(STUDY_MUTATION_ADAPTER, action, payload, signal);
return (action === "exam-start"
? withSafeStudyQuestionDefaults(result)
: result) as T;
}
withSafeStudyQuestionDefaults · normalizeStudyPayload
정규화 함수의 문항 필드 처리:
return {
...row,
correctAnswers: Array.isArray(row.correctAnswers) ? row.correctAnswers : [],
explanation: typeof row.explanation === "string" ? row.explanation : "",
scoringCriteria: Array.isArray(row.scoringCriteria) ? row.scoringCriteria : [],
requiredConcepts: Array.isArray(row.requiredConcepts) ? row.requiredConcepts : [],
acceptableAlternatives: Array.isArray(row.acceptableAlternatives) ? row.acceptableAlternatives : [],
deductionConditions: Array.isArray(row.deductionConditions) ? row.deductionConditions : [],
errorConditions: Array.isArray(row.errorConditions) ? row.errorConditions : [],
};
검증 결과
함수 실행
Node.js·TypeScript에서 원본 함수와 정규화기를 실행했다. ADsP·SQLD·SQLP·DAP·DAsP·BIGDATA 각각에 합성 문항 1개의 시작·제출 응답을 1회씩 넣었으며, API 로직은 대역을 사용했다. 시작 응답은 해설·정답 필드를 생략하고, 제출 응답은 Synthetic submitted explanation과 [1]을 넣었다.
| 비교 항목 | 수정 전 | 수정 후 |
|---|---|---|
시작 응답의 explanation.trim() 오류 | 6/6개 | 0/6개 |
| 시작 문항의 해설 | 필드 없음 | "" |
| 시작 문항의 정답 배열 | 필드 없음 | [] |
| 제출 응답의 정답·해설 보존 | 6/6개 | 6/6개 |
현재 원본 fetch 경로의 기본값·제출 보존 회귀 테스트도 fetch 대역 조건에서 1/1개 통과했다.
시작·선택·제출 화면
로컬 원본 코드 스냅샷에 합성 SQLD 문항 1개·보기 2개와 응답 대역을 연결했다. 1280×720 CSS px·DPR 2에서 수정 전후 시작을 각 1회, 수정 후 선택·제출을 1회 실행했다. 화면은 explanation.trim() 계산과 원본 LearningError를 사용하며, 제출 payload는 [1] 고정값이다.
| 단계 | 화면 결과 |
|---|---|
| 수정 전 시작 | 이 학습 화면을 준비하지 못했습니다. · 라디오 0개 |
| 수정 후 시작 | 모의고사 진행 중 · 1문항 · 라디오 2개 · 해설 0개 |
| LEFT JOIN 선택 | 선택한 라디오 값 1 |
| 수정 후 제출 | 모의고사 제출 결과 · 해설 1개 · 정답 선택지: [1] |
시작 응답에 해설이 없으면 오류 화면으로 전환됐다.
해설·정답의 기본값을 채운 뒤에는 문항 1개와 보기 2개가 표시됐다.
LEFT JOIN을 선택하고 제출하자 정답 배열 [1]과 “LEFT JOIN은 왼쪽 회원 테이블의 행을 보존합니다. 주문이 없는 회원도 결과에 남습니다.” 해설이 표시됐다.