Repository navigation
[global] 성적 정규화·OCR 입력 경로 회귀 테스트 추가 #506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
10933b0
chore(test): constants/ui 패키지에 tsx 기반 단위 테스트 러너 도입
s2yeons 7597b58
test(constants): getUsedAchievementFields 학기 사용여부 회귀 테스트 추가
s2yeons 6c421e1
refactor(ui): StepWrapper 페이로드 정규화 로직 분리 및 회귀 테스트 추가
s2yeons 35a61ee
refactor(ui): Step4Register 출결일수 재배열 로직 분리 및 회귀 테스트 추가
s2yeons 07da94c
docs(global): 유닛/E2E 테스트 실행 명령 안내 추가
s2yeons 7c3c661
docs(global): E2E 테스트 사전조건 문구 정정
s2yeons 1ad7bdb
refactor(ui): OCR 성취도 매핑 로직 분리 및 경계 테스트 추가
s2yeons File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| import assert from 'node:assert/strict'; | ||
| import test from 'node:test'; | ||
|
|
||
| import { getUsedAchievementFields } from './achievement'; | ||
|
|
||
| test('GED는 학기 입력 자체가 없으므로 전형/학년제와 무관하게 항상 빈 배열이다', () => { | ||
| assert.deepEqual( | ||
| getUsedAchievementFields({ | ||
| liberalSystem: '자유학년제', | ||
| graduationType: 'GED', | ||
| freeSemester: null, | ||
| }), | ||
| [], | ||
| ); | ||
| }); | ||
|
|
||
| test('자유학년제 졸업예정자는 1학년 전체(1-1·1-2)와 아직 없는 3-2가 빠진다', () => { | ||
| assert.deepEqual( | ||
| getUsedAchievementFields({ | ||
| liberalSystem: '자유학년제', | ||
| graduationType: 'CANDIDATE', | ||
| freeSemester: null, | ||
| }), | ||
| ['achievement2_1', 'achievement2_2', 'achievement3_1'], | ||
| ); | ||
| }); | ||
|
|
||
| test('자유학년제 졸업자는 1학년 전체(1-1·1-2)만 빠지고 3-2는 포함된다', () => { | ||
| assert.deepEqual( | ||
| getUsedAchievementFields({ | ||
| liberalSystem: '자유학년제', | ||
| graduationType: 'GRADUATE', | ||
| freeSemester: null, | ||
| }), | ||
| ['achievement2_1', 'achievement2_2', 'achievement3_1', 'achievement3_2'], | ||
| ); | ||
| }); | ||
|
|
||
| test('자유학기제 졸업예정자는 지정한 자유학기 한 학기와 아직 없는 3-2가 빠진다', () => { | ||
| assert.deepEqual( | ||
| getUsedAchievementFields({ | ||
| liberalSystem: '자유학기제', | ||
| graduationType: 'CANDIDATE', | ||
| freeSemester: '2-1', | ||
| }), | ||
| ['achievement1_1', 'achievement1_2', 'achievement2_2', 'achievement3_1'], | ||
| ); | ||
| }); | ||
|
|
||
| test('자유학기제 졸업자는 지정한 자유학기 한 학기만 빠진다', () => { | ||
| assert.deepEqual( | ||
| getUsedAchievementFields({ | ||
| liberalSystem: '자유학기제', | ||
| graduationType: 'GRADUATE', | ||
| freeSemester: '3-1', | ||
| }), | ||
| ['achievement1_1', 'achievement1_2', 'achievement2_1', 'achievement2_2', 'achievement3_2'], | ||
| ); | ||
| }); | ||
|
|
||
| test('자유학기제인데 아직 자유학기를 고르지 않았다면(freeSemester=null) 어떤 학기도 빠지지 않는다', () => { | ||
| assert.deepEqual( | ||
| getUsedAchievementFields({ | ||
| liberalSystem: '자유학기제', | ||
| graduationType: 'GRADUATE', | ||
| freeSemester: null, | ||
| }), | ||
| [ | ||
| 'achievement1_1', | ||
| 'achievement1_2', | ||
| 'achievement2_1', | ||
| 'achievement2_2', | ||
| 'achievement3_1', | ||
| 'achievement3_2', | ||
| ], | ||
| ); | ||
| }); | ||
|
|
||
| test('liberalSystem이 아직 정해지지 않은 null은 자유학기제로 취급한다 — freeSemester가 없으면 전부 포함', () => { | ||
| assert.deepEqual( | ||
| getUsedAchievementFields({ | ||
| liberalSystem: null, | ||
| graduationType: 'GRADUATE', | ||
| freeSemester: null, | ||
| }), | ||
| [ | ||
| 'achievement1_1', | ||
| 'achievement1_2', | ||
| 'achievement2_1', | ||
| 'achievement2_2', | ||
| 'achievement3_1', | ||
| 'achievement3_2', | ||
| ], | ||
| ); | ||
| }); | ||
|
|
||
| test('liberalSystem이 null이어도 freeSemester가 이미 정해졌다면 그 학기는 빠진다(자유학기제 취급 회귀 테스트)', () => { | ||
| assert.deepEqual( | ||
| getUsedAchievementFields({ | ||
| liberalSystem: null, | ||
| graduationType: 'GRADUATE', | ||
| freeSemester: '1-2', | ||
| }), | ||
| ['achievement1_1', 'achievement2_1', 'achievement2_2', 'achievement3_1', 'achievement3_2'], | ||
| ); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
packages/ui/src/components/StepWrapper/normalizeAchievements.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| import assert from 'node:assert/strict'; | ||
| import test from 'node:test'; | ||
|
|
||
| import { | ||
| FreeSemesterValueEnum, | ||
| GraduationTypeValueEnum, | ||
| LiberalSystemValueEnum, | ||
| Step4FormType, | ||
| } from '@repo/types'; | ||
|
|
||
| import { normalizeAchievements } from './normalizeAchievements'; | ||
|
|
||
| /** 테스트에서 다루는 필드만 채우고 나머지는 비워 둔다 — normalizeAchievements는 | ||
| * achievement1_1~3_2와 liberalSystem/freeSemester만 읽으므로 그 외 필드는 런타임에 | ||
| * 실제로 undefined일 수 있는 상태를 그대로 흉내낸다. */ | ||
| const values = (overrides: Partial<Step4FormType>): Step4FormType => | ||
| ({ | ||
| liberalSystem: null, | ||
| freeSemester: null, | ||
| ...overrides, | ||
| }) as Step4FormType; | ||
|
|
||
| test('자유학년제 졸업자가 OCR로 1-1·1-2에 값이 채워져도 null로 지운다(회귀 테스트)', () => { | ||
| // 실제 버그: OCR이 자유학년제 1학년 성취도를 0('없음')으로 채워 제출하면서 성적 계산이 틀어졌다. | ||
| const result = normalizeAchievements( | ||
| values({ | ||
| liberalSystem: LiberalSystemValueEnum.FREE_GRADE, | ||
| achievement1_1: [0, 0, 0], | ||
| achievement1_2: [0, 0, 0], | ||
| achievement2_1: [90, 85], | ||
| }), | ||
| GraduationTypeValueEnum.GRADUATE, | ||
| ); | ||
|
|
||
| assert.equal(result.achievement1_1, null); | ||
| assert.equal(result.achievement1_2, null); | ||
| assert.deepEqual(result.achievement2_1, [90, 85]); | ||
| }); | ||
|
|
||
| test('자유학년제 졸업예정자는 1-1·1-2뿐 아니라 아직 없는 3-2도 null로 지운다', () => { | ||
| const result = normalizeAchievements( | ||
| values({ | ||
| liberalSystem: LiberalSystemValueEnum.FREE_GRADE, | ||
| achievement1_1: [0], | ||
| achievement1_2: [0], | ||
| achievement3_2: [0], | ||
| }), | ||
| GraduationTypeValueEnum.CANDIDATE, | ||
| ); | ||
|
|
||
| assert.equal(result.achievement1_1, null); | ||
| assert.equal(result.achievement1_2, null); | ||
| assert.equal(result.achievement3_2, null); | ||
| }); | ||
|
|
||
| test('아직 입력이 시작되지 않은 학기(undefined)는 미사용 학기여도 undefined 그대로 둔다', () => { | ||
| // step 1~3 임시저장은 step 4 값이 전부 undefined인 채로 나가는데, 여기서 null로 바꾸면 | ||
| // 서버가 받아 오던 payload 모양이 달라진다. | ||
| const result = normalizeAchievements( | ||
| values({ | ||
| liberalSystem: LiberalSystemValueEnum.FREE_GRADE, | ||
| achievement1_1: undefined, | ||
| }), | ||
| GraduationTypeValueEnum.GRADUATE, | ||
| ); | ||
|
|
||
| assert.equal(result.achievement1_1, undefined); | ||
| }); | ||
|
|
||
| test('자유학기제는 지정한 자유학기 한 학기만 null로 지우고 나머지는 그대로 둔다', () => { | ||
| const result = normalizeAchievements( | ||
| values({ | ||
| liberalSystem: LiberalSystemValueEnum.FREE_SEMESTER, | ||
| freeSemester: FreeSemesterValueEnum['1-1'], | ||
| achievement1_1: [0, 0], | ||
| achievement1_2: [88, 92], | ||
| }), | ||
| GraduationTypeValueEnum.GRADUATE, | ||
| ); | ||
|
|
||
| assert.equal(result.achievement1_1, null); | ||
| assert.deepEqual(result.achievement1_2, [88, 92]); | ||
| }); | ||
|
|
||
| test('사용 중인 학기의 값은 그대로 통과시킨다', () => { | ||
| const result = normalizeAchievements( | ||
| values({ | ||
| liberalSystem: LiberalSystemValueEnum.FREE_SEMESTER, | ||
| freeSemester: null, | ||
| achievement1_1: [77, 80], | ||
| }), | ||
| GraduationTypeValueEnum.GRADUATE, | ||
| ); | ||
|
|
||
| assert.deepEqual(result.achievement1_1, [77, 80]); | ||
| }); | ||
42 changes: 42 additions & 0 deletions
42
packages/ui/src/components/StepWrapper/normalizeAchievements.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import { AchievementFieldType, getUsedAchievementFields } from '@repo/constants'; | ||
| import { GraduationTypeValueEnum, Step4FormType } from '@repo/types'; | ||
|
|
||
| /** | ||
| * 이번 전형에서 쓰지 않는 학기에 값이 남아 있으면 서버로 나가기 직전에 null로 지운다. | ||
| * | ||
| * 학기 열을 비우는 초기화는 입력 폼(FreeGradeForm/FreeSemesterForm)의 mount 시 한 번만 | ||
| * 돌기 때문에, 그 뒤에 값을 집어넣는 경로(생기부 OCR 자동입력)를 막지 못한다. 실제로 | ||
| * 자유학년제 졸업자가 OCR을 쓰면 1-1·1-2가 0('없음')으로 채워진 채 제출돼 성적 계산이 | ||
| * 틀어졌다. 입력 경로마다 막는 것과 별개로, 페이로드를 만드는 이 마지막 지점에서 한 번 | ||
| * 더 거른다. | ||
| * | ||
| * 아직 입력이 시작되지 않은 학기(undefined)는 undefined 그대로 둔다 — step 1~3 임시저장은 | ||
| * step 4 값이 전부 비어 있는 채로 나가는데, 이걸 null로 바꾸면 그동안 서버가 받아 오던 | ||
| * payload 모양이 달라진다. | ||
| */ | ||
| export const normalizeAchievements = ( | ||
| values: Step4FormType, | ||
| graduationType: GraduationTypeValueEnum, | ||
| ): Record<AchievementFieldType, number[] | null | undefined> => { | ||
| const usedFields = getUsedAchievementFields({ | ||
| liberalSystem: values.liberalSystem, | ||
| graduationType, | ||
| freeSemester: values.freeSemester, | ||
| }); | ||
|
|
||
| const normalize = (field: AchievementFieldType) => { | ||
| // 스키마상 number[] | null이지만, 아직 기본값이 채워지지 않은 학기는 런타임에 undefined다 | ||
| const value = values[field] as number[] | null | undefined; | ||
| if (value === undefined || usedFields.includes(field)) return value; | ||
| return null; | ||
| }; | ||
|
|
||
| return { | ||
| achievement1_1: normalize('achievement1_1'), | ||
| achievement1_2: normalize('achievement1_2'), | ||
| achievement2_1: normalize('achievement2_1'), | ||
| achievement2_2: normalize('achievement2_2'), | ||
| achievement3_1: normalize('achievement3_1'), | ||
| achievement3_2: normalize('achievement3_2'), | ||
| }; | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.