@tipp/ui 2.9.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/dist/app/index.js +17 -17
  2. package/dist/app/platform/edit-coaching-time.js +17 -17
  3. package/dist/app/platform/edit-service-type.js +17 -17
  4. package/dist/app/platform/goal-manage-card-edit.js +17 -17
  5. package/dist/app/platform/on-offline-radio-card.js +17 -17
  6. package/dist/app/platform/reservation-card.js +17 -17
  7. package/dist/app/platform/session-card.js +17 -17
  8. package/dist/app/roleplay/chart/session-chart.cjs +351 -0
  9. package/dist/app/roleplay/chart/session-chart.cjs.map +1 -0
  10. package/dist/app/roleplay/chart/session-chart.d.cts +16 -0
  11. package/dist/app/roleplay/chart/session-chart.d.ts +16 -0
  12. package/dist/app/roleplay/chart/session-chart.js +14 -0
  13. package/dist/app/roleplay/chart/session-chart.js.map +1 -0
  14. package/dist/app/roleplay/index.cjs +5706 -0
  15. package/dist/app/roleplay/index.cjs.map +1 -0
  16. package/dist/app/roleplay/index.d.cts +4 -0
  17. package/dist/app/roleplay/index.d.ts +4 -0
  18. package/dist/app/roleplay/index.js +182 -0
  19. package/dist/app/roleplay/index.js.map +1 -0
  20. package/dist/app/roleplay/parts/analysis-section.cjs +2178 -0
  21. package/dist/app/roleplay/parts/analysis-section.cjs.map +1 -0
  22. package/dist/app/roleplay/parts/analysis-section.d.cts +17 -0
  23. package/dist/app/roleplay/parts/analysis-section.d.ts +17 -0
  24. package/dist/app/roleplay/parts/analysis-section.js +97 -0
  25. package/dist/app/roleplay/parts/analysis-section.js.map +1 -0
  26. package/dist/app/roleplay/parts/coaching-panel.cjs +2191 -0
  27. package/dist/app/roleplay/parts/coaching-panel.cjs.map +1 -0
  28. package/dist/app/roleplay/parts/coaching-panel.d.cts +8 -0
  29. package/dist/app/roleplay/parts/coaching-panel.d.ts +8 -0
  30. package/dist/app/roleplay/parts/coaching-panel.js +95 -0
  31. package/dist/app/roleplay/parts/coaching-panel.js.map +1 -0
  32. package/dist/app/roleplay/parts/content.cjs +4683 -0
  33. package/dist/app/roleplay/parts/content.cjs.map +1 -0
  34. package/dist/app/roleplay/parts/content.d.cts +15 -0
  35. package/dist/app/roleplay/parts/content.d.ts +15 -0
  36. package/dist/app/roleplay/parts/content.js +174 -0
  37. package/dist/app/roleplay/parts/content.js.map +1 -0
  38. package/dist/app/roleplay/parts/header.cjs +2300 -0
  39. package/dist/app/roleplay/parts/header.cjs.map +1 -0
  40. package/dist/app/roleplay/parts/header.d.cts +13 -0
  41. package/dist/app/roleplay/parts/header.d.ts +13 -0
  42. package/dist/app/roleplay/parts/header.js +96 -0
  43. package/dist/app/roleplay/parts/header.js.map +1 -0
  44. package/dist/app/roleplay/parts/score-summary.cjs +2276 -0
  45. package/dist/app/roleplay/parts/score-summary.cjs.map +1 -0
  46. package/dist/app/roleplay/parts/score-summary.d.cts +12 -0
  47. package/dist/app/roleplay/parts/score-summary.d.ts +12 -0
  48. package/dist/app/roleplay/parts/score-summary.js +96 -0
  49. package/dist/app/roleplay/parts/score-summary.js.map +1 -0
  50. package/dist/app/roleplay/parts/score-trend-section.cjs +3686 -0
  51. package/dist/app/roleplay/parts/score-trend-section.cjs.map +1 -0
  52. package/dist/app/roleplay/parts/score-trend-section.d.cts +14 -0
  53. package/dist/app/roleplay/parts/score-trend-section.d.ts +14 -0
  54. package/dist/app/roleplay/parts/score-trend-section.js +168 -0
  55. package/dist/app/roleplay/parts/score-trend-section.js.map +1 -0
  56. package/dist/app/roleplay/parts/script-transcript.cjs +2288 -0
  57. package/dist/app/roleplay/parts/script-transcript.cjs.map +1 -0
  58. package/dist/app/roleplay/parts/script-transcript.d.cts +15 -0
  59. package/dist/app/roleplay/parts/script-transcript.d.ts +15 -0
  60. package/dist/app/roleplay/parts/script-transcript.js +96 -0
  61. package/dist/app/roleplay/parts/script-transcript.js.map +1 -0
  62. package/dist/app/roleplay/parts/section-nav.cjs +2433 -0
  63. package/dist/app/roleplay/parts/section-nav.cjs.map +1 -0
  64. package/dist/app/roleplay/parts/section-nav.d.cts +15 -0
  65. package/dist/app/roleplay/parts/section-nav.d.ts +15 -0
  66. package/dist/app/roleplay/parts/section-nav.js +96 -0
  67. package/dist/app/roleplay/parts/section-nav.js.map +1 -0
  68. package/dist/app/roleplay/parts/session-replay-dialog.cjs +2598 -0
  69. package/dist/app/roleplay/parts/session-replay-dialog.cjs.map +1 -0
  70. package/dist/app/roleplay/parts/session-replay-dialog.d.cts +14 -0
  71. package/dist/app/roleplay/parts/session-replay-dialog.d.ts +14 -0
  72. package/dist/app/roleplay/parts/session-replay-dialog.js +97 -0
  73. package/dist/app/roleplay/parts/session-replay-dialog.js.map +1 -0
  74. package/dist/app/roleplay/parts/step-detail.cjs +2311 -0
  75. package/dist/app/roleplay/parts/step-detail.cjs.map +1 -0
  76. package/dist/app/roleplay/parts/step-detail.d.cts +9 -0
  77. package/dist/app/roleplay/parts/step-detail.d.ts +9 -0
  78. package/dist/app/roleplay/parts/step-detail.js +96 -0
  79. package/dist/app/roleplay/parts/step-detail.js.map +1 -0
  80. package/dist/app/roleplay/parts/step-radar.cjs +463 -0
  81. package/dist/app/roleplay/parts/step-radar.cjs.map +1 -0
  82. package/dist/app/roleplay/parts/step-radar.d.cts +8 -0
  83. package/dist/app/roleplay/parts/step-radar.d.ts +8 -0
  84. package/dist/app/roleplay/parts/step-radar.js +14 -0
  85. package/dist/app/roleplay/parts/step-radar.js.map +1 -0
  86. package/dist/app/roleplay/parts/step-summary.cjs +2651 -0
  87. package/dist/app/roleplay/parts/step-summary.cjs.map +1 -0
  88. package/dist/app/roleplay/parts/step-summary.d.cts +8 -0
  89. package/dist/app/roleplay/parts/step-summary.d.ts +8 -0
  90. package/dist/app/roleplay/parts/step-summary.js +101 -0
  91. package/dist/app/roleplay/parts/step-summary.js.map +1 -0
  92. package/dist/app/roleplay/rp-session-detail.cjs +5703 -0
  93. package/dist/app/roleplay/rp-session-detail.cjs.map +1 -0
  94. package/dist/app/roleplay/rp-session-detail.d.cts +17 -0
  95. package/dist/app/roleplay/rp-session-detail.d.ts +17 -0
  96. package/dist/app/roleplay/rp-session-detail.js +179 -0
  97. package/dist/app/roleplay/rp-session-detail.js.map +1 -0
  98. package/dist/app/roleplay/types.cjs +19 -0
  99. package/dist/app/roleplay/types.cjs.map +1 -0
  100. package/dist/app/roleplay/types.d.cts +42 -0
  101. package/dist/app/roleplay/types.d.ts +42 -0
  102. package/dist/app/roleplay/types.js +2 -0
  103. package/dist/app/roleplay/types.js.map +1 -0
  104. package/dist/app/roleplay/utils/report-parse.cjs +285 -0
  105. package/dist/app/roleplay/utils/report-parse.cjs.map +1 -0
  106. package/dist/app/roleplay/utils/report-parse.d.cts +57 -0
  107. package/dist/app/roleplay/utils/report-parse.d.ts +57 -0
  108. package/dist/app/roleplay/utils/report-parse.js +8 -0
  109. package/dist/app/roleplay/utils/report-parse.js.map +1 -0
  110. package/dist/app/roleplay/utils/report-parse.test.cjs +491 -0
  111. package/dist/app/roleplay/utils/report-parse.test.cjs.map +1 -0
  112. package/dist/app/roleplay/utils/report-parse.test.d.cts +2 -0
  113. package/dist/app/roleplay/utils/report-parse.test.d.ts +2 -0
  114. package/dist/app/roleplay/utils/report-parse.test.js +237 -0
  115. package/dist/app/roleplay/utils/report-parse.test.js.map +1 -0
  116. package/dist/app/roleplay/utils/session-utils.cjs +204 -0
  117. package/dist/app/roleplay/utils/session-utils.cjs.map +1 -0
  118. package/dist/app/roleplay/utils/session-utils.d.cts +38 -0
  119. package/dist/app/roleplay/utils/session-utils.d.ts +38 -0
  120. package/dist/app/roleplay/utils/session-utils.js +46 -0
  121. package/dist/app/roleplay/utils/session-utils.js.map +1 -0
  122. package/dist/chunk-2TOASXPK.js +131 -0
  123. package/dist/chunk-2TOASXPK.js.map +1 -0
  124. package/dist/chunk-3UGBMKSQ.js +166 -0
  125. package/dist/chunk-3UGBMKSQ.js.map +1 -0
  126. package/dist/chunk-4YAA25AV.js +161 -0
  127. package/dist/chunk-4YAA25AV.js.map +1 -0
  128. package/dist/chunk-5QIT4URV.js +87 -0
  129. package/dist/chunk-5QIT4URV.js.map +1 -0
  130. package/dist/chunk-6YIKOYBQ.js +185 -0
  131. package/dist/chunk-6YIKOYBQ.js.map +1 -0
  132. package/dist/chunk-7MXY3D3Z.js +84 -0
  133. package/dist/chunk-7MXY3D3Z.js.map +1 -0
  134. package/dist/chunk-EBFQFOUQ.js +138 -0
  135. package/dist/chunk-EBFQFOUQ.js.map +1 -0
  136. package/dist/chunk-EHDUKUML.js +183 -0
  137. package/dist/chunk-EHDUKUML.js.map +1 -0
  138. package/dist/{chunk-NEJCZABQ.js → chunk-FJEDSUGU.js} +7 -7
  139. package/dist/chunk-GA6W62DO.js +101 -0
  140. package/dist/chunk-GA6W62DO.js.map +1 -0
  141. package/dist/chunk-KJPBBBVK.js +142 -0
  142. package/dist/chunk-KJPBBBVK.js.map +1 -0
  143. package/dist/{chunk-TQTCT7DC.js → chunk-LRNO4W2P.js} +5 -5
  144. package/dist/{chunk-6DWUGXSY.js → chunk-N242VJOP.js} +2 -2
  145. package/dist/chunk-NKW6BO5R.js +264 -0
  146. package/dist/chunk-NKW6BO5R.js.map +1 -0
  147. package/dist/{chunk-R5UDNKV2.js → chunk-O332K3SW.js} +2 -2
  148. package/dist/chunk-OUP3VFXG.js +1 -0
  149. package/dist/chunk-OUP3VFXG.js.map +1 -0
  150. package/dist/chunk-QMAINJ4G.js +190 -0
  151. package/dist/chunk-QMAINJ4G.js.map +1 -0
  152. package/dist/{chunk-KLJVIEFW.js → chunk-SOKGYD5B.js} +7 -7
  153. package/dist/chunk-T7LKMJVA.js +142 -0
  154. package/dist/chunk-T7LKMJVA.js.map +1 -0
  155. package/dist/chunk-TFJJKM3W.js +261 -0
  156. package/dist/chunk-TFJJKM3W.js.map +1 -0
  157. package/dist/{chunk-KQ3ME6XY.js → chunk-U2EAXN3Y.js} +4 -4
  158. package/dist/chunk-UXAEOCOH.js +150 -0
  159. package/dist/chunk-UXAEOCOH.js.map +1 -0
  160. package/dist/chunk-XM4GWPXC.js +236 -0
  161. package/dist/chunk-XM4GWPXC.js.map +1 -0
  162. package/dist/index.css +76 -0
  163. package/dist/index.js +32 -32
  164. package/dist/molecules/curriculumV2/CurriculumContents/CurriculumReviewSteps/ReviewQuestion/index.js +5 -5
  165. package/dist/molecules/curriculumV2/CurriculumContents/CurriculumReviewSteps/ReviewQuestion/review-question-body.js +2 -2
  166. package/dist/molecules/curriculumV2/CurriculumContents/CurriculumReviewSteps/index.js +7 -7
  167. package/dist/molecules/curriculumV2/CurriculumContents/curriculum-review.js +8 -8
  168. package/dist/molecules/curriculumV2/CurriculumContents/index.js +9 -9
  169. package/dist/molecules/curriculumV2/index.js +11 -11
  170. package/dist/molecules/dynamic-form-editor.js +2 -2
  171. package/dist/molecules/index.js +29 -29
  172. package/package.json +28 -2
  173. package/src/app/roleplay/chart/session-chart.tsx +185 -0
  174. package/src/app/roleplay/index.ts +8 -0
  175. package/src/app/roleplay/parts/analysis-section.tsx +98 -0
  176. package/src/app/roleplay/parts/coaching-panel.tsx +101 -0
  177. package/src/app/roleplay/parts/content.tsx +146 -0
  178. package/src/app/roleplay/parts/header.tsx +168 -0
  179. package/src/app/roleplay/parts/score-summary.tsx +158 -0
  180. package/src/app/roleplay/parts/score-trend-section.tsx +255 -0
  181. package/src/app/roleplay/parts/script-transcript.tsx +190 -0
  182. package/src/app/roleplay/parts/section-nav.tsx +299 -0
  183. package/src/app/roleplay/parts/session-replay-dialog.tsx +168 -0
  184. package/src/app/roleplay/parts/step-detail.tsx +173 -0
  185. package/src/app/roleplay/parts/step-radar.tsx +80 -0
  186. package/src/app/roleplay/parts/step-summary.tsx +149 -0
  187. package/src/app/roleplay/rp-session-detail.tsx +176 -0
  188. package/src/app/roleplay/types.ts +61 -0
  189. package/src/app/roleplay/utils/report-parse.test.ts +268 -0
  190. package/src/app/roleplay/utils/report-parse.ts +443 -0
  191. package/src/app/roleplay/utils/session-utils.ts +215 -0
  192. package/src/constrant/trainingLog.dummy.json +1 -0
  193. /package/dist/{chunk-NEJCZABQ.js.map → chunk-FJEDSUGU.js.map} +0 -0
  194. /package/dist/{chunk-TQTCT7DC.js.map → chunk-LRNO4W2P.js.map} +0 -0
  195. /package/dist/{chunk-6DWUGXSY.js.map → chunk-N242VJOP.js.map} +0 -0
  196. /package/dist/{chunk-R5UDNKV2.js.map → chunk-O332K3SW.js.map} +0 -0
  197. /package/dist/{chunk-KLJVIEFW.js.map → chunk-SOKGYD5B.js.map} +0 -0
  198. /package/dist/{chunk-KQ3ME6XY.js.map → chunk-U2EAXN3Y.js.map} +0 -0
@@ -0,0 +1,268 @@
1
+ import { parseReportData } from './report-parse';
2
+
3
+ describe('parseReportData', () => {
4
+ it('신버전: output_text 래퍼 + comprehensive_analysis + {name}_items 를 파싱한다', () => {
5
+ const reportData = {
6
+ output_text: {
7
+ todayDate: '2026-06-19',
8
+ summary: {
9
+ total_score: 82,
10
+ deduction: { score: -3, basis: '시간 초과' },
11
+ },
12
+ comprehensive_analysis: {
13
+ comment: '전반적으로 우수합니다.',
14
+ strengths: [{ title1: '경청', detail1: '잘 들었다' }],
15
+ improvements: [{ title1: '질문', detail1: '더 구체적으로' }],
16
+ },
17
+ eval_items: [
18
+ {
19
+ own_score: 8,
20
+ own_max_score: 10,
21
+ own_quality: '충족',
22
+ own_basis: '근거',
23
+ own_issue: '아쉬움',
24
+ own_improve: '개선',
25
+ own_example: '예시 스크립트',
26
+ },
27
+ ],
28
+ skills: [
29
+ { skill1: '미러링', description1: '상대 말 반영', example1: '예시' },
30
+ ],
31
+ },
32
+ ai_traininglog_id: 'log-1',
33
+ };
34
+ const guideline = {
35
+ value: [{ title: '자기이해', description: '설명', example: '모범' }],
36
+ version: 1,
37
+ };
38
+
39
+ const r = parseReportData(reportData, guideline);
40
+
41
+ expect(r.todayDate).toBe('2026-06-19');
42
+ expect(r.totalScore).toBe(82);
43
+ expect(r.comment).toBe('전반적으로 우수합니다.');
44
+ expect(r.deduction).toEqual({ score: -3, basis: '시간 초과' });
45
+ expect(r.strengths).toEqual([{ title: '경청', detail: '잘 들었다' }]);
46
+ expect(r.improvements).toEqual([{ title: '질문', detail: '더 구체적으로' }]);
47
+ expect(r.skills).toEqual([
48
+ { title: '미러링', description: '상대 말 반영', example: '예시' },
49
+ ]);
50
+
51
+ expect(r.steps).toHaveLength(1);
52
+ const step = r.steps[0];
53
+ // own_max_score 가 '_score' 보다 '_max_score' 로 먼저 매칭되어야 한다.
54
+ expect(step.key).toBe('own');
55
+ expect(step.score).toBe(8);
56
+ expect(step.maxScore).toBe(10);
57
+ expect(step.quality).toBe('충족');
58
+ expect(step.basis).toBe('근거');
59
+ // guideline 길이가 단계 수와 일치하므로 라벨/설명/예시를 사용한다.
60
+ expect(step.label).toBe('자기이해');
61
+ expect(step.description).toBe('설명');
62
+ expect(step.idealExample).toBe('모범');
63
+ });
64
+
65
+ it('구버전: summary.good_point / improvement / basis 폴백을 파싱한다', () => {
66
+ const reportData = {
67
+ output_text: {
68
+ summary: {
69
+ total_score: 70,
70
+ basis: '구버전 코멘트',
71
+ good_point: [{ title1: '강점A', detail1: '내용A' }],
72
+ improvement: [{ title1: '개선A', detail1: '내용B' }],
73
+ },
74
+ skill_items: [{ a_score: 5, a_quality: 'X 매우 미흡' }],
75
+ },
76
+ };
77
+
78
+ const r = parseReportData(reportData, null);
79
+
80
+ expect(r.totalScore).toBe(70);
81
+ expect(r.comment).toBe('구버전 코멘트');
82
+ expect(r.strengths).toEqual([{ title: '강점A', detail: '내용A' }]);
83
+ expect(r.improvements).toEqual([{ title: '개선A', detail: '내용B' }]);
84
+
85
+ expect(r.steps).toHaveLength(1);
86
+ const step = r.steps[0];
87
+ // guideline 이 없으므로 humanize 폴백.
88
+ expect(step.label).toBe('A');
89
+ // 비표준 등급은 quality=null, qualityLabel 에 원문 보존.
90
+ expect(step.quality).toBeNull();
91
+ expect(step.qualityLabel).toBe('X 매우 미흡');
92
+ });
93
+
94
+ it('래퍼(output_text)가 없으면 reportData 자체를 루트로 사용한다', () => {
95
+ const reportData = {
96
+ summary: { total_score: 55 },
97
+ eval_items: [{ x_score: 3 }],
98
+ };
99
+
100
+ const r = parseReportData(reportData, null);
101
+
102
+ expect(r.totalScore).toBe(55);
103
+ expect(r.steps).toHaveLength(1);
104
+ expect(r.steps[0].score).toBe(3);
105
+ });
106
+
107
+ it('잘못된/빈 입력은 빈 리포트로 안전 처리한다', () => {
108
+ const r = parseReportData(null, undefined);
109
+ expect(r.totalScore).toBeNull();
110
+ expect(r.comment).toBeNull();
111
+ expect(r.steps).toEqual([]);
112
+ expect(r.strengths).toEqual([]);
113
+ expect(r.improvements).toEqual([]);
114
+ expect(r.skills).toEqual([]);
115
+ expect(r.deduction).toBeNull();
116
+ });
117
+
118
+ it('guideline 길이가 단계 수와 불일치하면 humanize 폴백한다', () => {
119
+ const reportData = {
120
+ output_text: {
121
+ eval_items: [{ nurturing_org_score: 7, own_score: 9 }],
122
+ },
123
+ };
124
+ // 단계는 2개(nurturing_org, own)인데 guideline 은 1개 → 불일치 → 폴백.
125
+ const guideline = { value: [{ title: '단 하나' }], version: 1 };
126
+
127
+ const r = parseReportData(reportData, guideline);
128
+
129
+ expect(r.steps).toHaveLength(2);
130
+ expect(r.steps[0].label).toBe('Nurturing Org');
131
+ expect(r.steps[1].label).toBe('Own');
132
+ });
133
+
134
+ it('displayMeta.steps: order 정렬 + key 매칭 + initial/maxValue/title/description 적용', () => {
135
+ const reportData = {
136
+ output_text: {
137
+ eval_items: [
138
+ {
139
+ // 등장 순서는 progress 먼저, opening 나중 — displayMeta.order 로 뒤집힌다.
140
+ progress_score: 6,
141
+ progress_basis: 'p근거',
142
+ opening_score: 9,
143
+ opening_basis: 'o근거',
144
+ },
145
+ ],
146
+ },
147
+ };
148
+ const displayMeta = {
149
+ title: 'OPENFANS 코칭',
150
+ description: '단계 코칭 프레임워크',
151
+ steps: [
152
+ {
153
+ order: 1,
154
+ key: 'opening',
155
+ initial: 'O',
156
+ displayName: '오프닝',
157
+ maxValue: 10,
158
+ description: '첫 인상',
159
+ },
160
+ {
161
+ order: 2,
162
+ key: 'progress',
163
+ initial: 'P',
164
+ displayName: '진행',
165
+ maxValue: 20,
166
+ description: '',
167
+ },
168
+ ],
169
+ };
170
+
171
+ const r = parseReportData(reportData, null, displayMeta);
172
+
173
+ expect(r.frameworkTitle).toBe('OPENFANS 코칭');
174
+ expect(r.frameworkDescription).toBe('단계 코칭 프레임워크');
175
+
176
+ expect(r.steps).toHaveLength(2);
177
+ // order 정렬: opening(1) → progress(2). reportData 등장 순서(progress 먼저)와 무관.
178
+ expect(r.steps[0].key).toBe('opening');
179
+ expect(r.steps[0].label).toBe('오프닝');
180
+ expect(r.steps[0].initial).toBe('O');
181
+ expect(r.steps[0].score).toBe(9);
182
+ expect(r.steps[0].basis).toBe('o근거');
183
+ // reportData 에 max 없음 → displayMeta.maxValue 폴백.
184
+ expect(r.steps[0].maxScore).toBe(10);
185
+ expect(r.steps[0].description).toBe('첫 인상');
186
+
187
+ expect(r.steps[1].key).toBe('progress');
188
+ expect(r.steps[1].initial).toBe('P');
189
+ expect(r.steps[1].maxScore).toBe(20);
190
+ // displayMeta.steps.description 이 빈 문자열 + guideline 없음 → null.
191
+ expect(r.steps[1].description).toBeNull();
192
+ });
193
+
194
+ it('displayMeta: reportData 의 실제 max_score 가 maxValue 보다 우선한다', () => {
195
+ const reportData = {
196
+ output_text: { eval_items: [{ opening_score: 8, opening_max_score: 12 }] },
197
+ };
198
+ const displayMeta = {
199
+ steps: [
200
+ {
201
+ order: 0,
202
+ key: 'opening',
203
+ initial: 'O',
204
+ displayName: '오프닝',
205
+ maxValue: 100,
206
+ description: '',
207
+ },
208
+ ],
209
+ };
210
+
211
+ const r = parseReportData(reportData, null, displayMeta);
212
+ expect(r.steps[0].maxScore).toBe(12);
213
+ });
214
+
215
+ it('displayMeta.steps 에 있으나 reportData 에 없는 key 도 행을 유지한다(점수 null)', () => {
216
+ const reportData = { output_text: { eval_items: [{ opening_score: 7 }] } };
217
+ const displayMeta = {
218
+ steps: [
219
+ {
220
+ order: 0,
221
+ key: 'opening',
222
+ initial: 'O',
223
+ displayName: '오프닝',
224
+ maxValue: 10,
225
+ description: '',
226
+ },
227
+ {
228
+ order: 1,
229
+ key: 'closing',
230
+ initial: 'C',
231
+ displayName: '클로징',
232
+ maxValue: 10,
233
+ description: '',
234
+ },
235
+ ],
236
+ };
237
+
238
+ const r = parseReportData(reportData, null, displayMeta);
239
+ expect(r.steps).toHaveLength(2);
240
+ expect(r.steps[1].key).toBe('closing');
241
+ expect(r.steps[1].score).toBeNull();
242
+ // 데이터가 없어도 displayMeta.maxValue 폴백은 적용.
243
+ expect(r.steps[1].maxScore).toBe(10);
244
+ });
245
+
246
+ it('displayMeta 가 없으면 frameworkTitle/Description=null, step.initial=null (폴백)', () => {
247
+ const reportData = { output_text: { eval_items: [{ own_score: 5 }] } };
248
+ const r = parseReportData(reportData, null);
249
+ expect(r.frameworkTitle).toBeNull();
250
+ expect(r.frameworkDescription).toBeNull();
251
+ expect(r.steps[0].initial).toBeNull();
252
+ });
253
+
254
+ it('displayMeta.title/description 만 있고 steps 가 비면 텍스트만 적용하고 단계는 폴백한다', () => {
255
+ const reportData = {
256
+ output_text: { eval_items: [{ own_score: 5, progress_score: 6 }] },
257
+ };
258
+ const displayMeta = { title: '제목만', description: '설명만', steps: [] };
259
+
260
+ const r = parseReportData(reportData, null, displayMeta);
261
+ expect(r.frameworkTitle).toBe('제목만');
262
+ expect(r.frameworkDescription).toBe('설명만');
263
+ // steps 비었으므로 폴백(등장 순서 + humanize).
264
+ expect(r.steps).toHaveLength(2);
265
+ expect(r.steps[0].key).toBe('own');
266
+ expect(r.steps[0].initial).toBeNull();
267
+ });
268
+ });