@ncds/ui-admin 1.8.21-alpha.4 → 1.8.21-alpha.6

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 (75) hide show
  1. package/dist/cjs/assets/scripts/notification/FullWidthNotification.js +17 -6
  2. package/dist/cjs/assets/scripts/notification/MessageNotification.js +10 -48
  3. package/dist/cjs/assets/scripts/notification/const/classNames.js +3 -0
  4. package/dist/cjs/assets/scripts/notification/const/index.js +1 -1
  5. package/dist/cjs/assets/scripts/notification/const/sizes.js +4 -2
  6. package/dist/cjs/assets/scripts/notification/messageTemplate.js +106 -0
  7. package/dist/cjs/assets/scripts/notification/utils.js +40 -4
  8. package/dist/cjs/constant/notification.js +21 -0
  9. package/dist/cjs/src/components/overlays/notification/FullWidthNotification.js +3 -1
  10. package/dist/cjs/src/components/overlays/notification/MessageNotification.js +14 -54
  11. package/dist/cjs/src/components/overlays/notification/MessageNotificationParts.js +92 -0
  12. package/dist/cjs/src/components/overlays/notification/__tests__/MessageNotification.test.js +246 -0
  13. package/dist/cjs/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +209 -0
  14. package/dist/cjs/src/generated/scoped-css.js +1 -1
  15. package/dist/esm/assets/scripts/notification/FullWidthNotification.js +17 -6
  16. package/dist/esm/assets/scripts/notification/MessageNotification.js +12 -50
  17. package/dist/esm/assets/scripts/notification/const/classNames.js +3 -0
  18. package/dist/esm/assets/scripts/notification/const/index.js +3 -2
  19. package/dist/esm/assets/scripts/notification/const/sizes.js +4 -2
  20. package/dist/esm/assets/scripts/notification/messageTemplate.js +100 -0
  21. package/dist/esm/assets/scripts/notification/utils.js +39 -4
  22. package/dist/esm/constant/notification.js +15 -0
  23. package/dist/esm/src/components/overlays/notification/FullWidthNotification.js +3 -1
  24. package/dist/esm/src/components/overlays/notification/MessageNotification.js +15 -55
  25. package/dist/esm/src/components/overlays/notification/MessageNotificationParts.js +85 -0
  26. package/dist/esm/src/components/overlays/notification/__tests__/MessageNotification.test.js +243 -0
  27. package/dist/esm/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +206 -0
  28. package/dist/esm/src/generated/scoped-css.js +1 -1
  29. package/dist/temp/assets/scripts/notification/FullWidthNotification.d.ts +6 -0
  30. package/dist/temp/assets/scripts/notification/FullWidthNotification.js +14 -5
  31. package/dist/temp/assets/scripts/notification/MessageNotification.d.ts +0 -3
  32. package/dist/temp/assets/scripts/notification/MessageNotification.js +9 -44
  33. package/dist/temp/assets/scripts/notification/const/classNames.d.ts +1 -0
  34. package/dist/temp/assets/scripts/notification/const/classNames.js +3 -0
  35. package/dist/temp/assets/scripts/notification/const/index.d.ts +3 -3
  36. package/dist/temp/assets/scripts/notification/const/index.js +3 -2
  37. package/dist/temp/assets/scripts/notification/const/sizes.d.ts +2 -2
  38. package/dist/temp/assets/scripts/notification/const/sizes.js +4 -2
  39. package/dist/temp/assets/scripts/notification/const/types.d.ts +30 -2
  40. package/dist/temp/assets/scripts/notification/messageTemplate.d.ts +15 -0
  41. package/dist/temp/assets/scripts/notification/messageTemplate.js +79 -0
  42. package/dist/temp/assets/scripts/notification/utils.d.ts +19 -2
  43. package/dist/temp/assets/scripts/notification/utils.js +39 -5
  44. package/dist/temp/constant/notification.d.ts +15 -0
  45. package/dist/temp/constant/notification.js +15 -0
  46. package/dist/temp/src/components/overlays/notification/FullWidthNotification.d.ts +6 -0
  47. package/dist/temp/src/components/overlays/notification/FullWidthNotification.js +3 -2
  48. package/dist/temp/src/components/overlays/notification/MessageNotification.d.ts +6 -0
  49. package/dist/temp/src/components/overlays/notification/MessageNotification.js +4 -13
  50. package/dist/temp/src/components/overlays/notification/MessageNotificationParts.d.ts +28 -0
  51. package/dist/temp/src/components/overlays/notification/MessageNotificationParts.js +28 -0
  52. package/dist/temp/src/components/overlays/notification/Notification.d.ts +11 -2
  53. package/dist/temp/src/components/overlays/notification/__tests__/MessageNotification.test.d.ts +1 -0
  54. package/dist/temp/src/components/overlays/notification/__tests__/MessageNotification.test.js +190 -0
  55. package/dist/temp/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.d.ts +1 -0
  56. package/dist/temp/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +160 -0
  57. package/dist/temp/src/generated/scoped-css.js +1 -1
  58. package/dist/types/assets/scripts/notification/FullWidthNotification.d.ts +6 -0
  59. package/dist/types/assets/scripts/notification/MessageNotification.d.ts +0 -3
  60. package/dist/types/assets/scripts/notification/const/classNames.d.ts +1 -0
  61. package/dist/types/assets/scripts/notification/const/index.d.ts +3 -3
  62. package/dist/types/assets/scripts/notification/const/sizes.d.ts +2 -2
  63. package/dist/types/assets/scripts/notification/const/types.d.ts +30 -2
  64. package/dist/types/assets/scripts/notification/messageTemplate.d.ts +15 -0
  65. package/dist/types/assets/scripts/notification/utils.d.ts +19 -2
  66. package/dist/types/constant/notification.d.ts +15 -0
  67. package/dist/types/src/components/overlays/notification/FullWidthNotification.d.ts +6 -0
  68. package/dist/types/src/components/overlays/notification/MessageNotification.d.ts +6 -0
  69. package/dist/types/src/components/overlays/notification/MessageNotificationParts.d.ts +28 -0
  70. package/dist/types/src/components/overlays/notification/Notification.d.ts +11 -2
  71. package/dist/types/src/components/overlays/notification/__tests__/MessageNotification.test.d.ts +1 -0
  72. package/dist/types/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.d.ts +1 -0
  73. package/dist/ui-admin/assets/styles/style.css +21 -33
  74. package/dist/ui-admin/assets/styles/style.scoped.css +21 -33
  75. package/package.json +1 -1
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+
3
+ var _react = require("react");
4
+ var _client = require("react-dom/client");
5
+ var _testUtils = require("react-dom/test-utils");
6
+ var _vitest = require("vitest");
7
+ var _MessageNotification = require("../MessageNotification");
8
+ // @vitest-environment jsdom
9
+
10
+ // React 18에서 act() 사용 시 필요한 플래그 (미설정 시 console.error 경고 발생)
11
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
12
+ const SELECTOR = {
13
+ ROOT: '.ncua-message-notification',
14
+ CONTENT: '.ncua-message-notification__content',
15
+ CONTENT_WRAPPER: '.ncua-message-notification__content-wrapper',
16
+ ICON: '.ncua-message-notification__icon',
17
+ ACTIONS_CONTAINER: '.ncua-message-notification__actions-container',
18
+ ACTIONS: '.ncua-message-notification__actions',
19
+ FOOTER_CONTAINER: '.ncua-message-notification__footer-container',
20
+ HIDE_LINK: '.ncua-message-notification__hide-link',
21
+ CLOSE_BUTTON: '.ncua-message-notification__close-button'
22
+ };
23
+ const mounted = [];
24
+ function renderMessageNotification() {
25
+ let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
26
+ const container = document.createElement('div');
27
+ document.body.appendChild(container);
28
+ const root = (0, _client.createRoot)(container);
29
+ (0, _testUtils.act)(() => {
30
+ root.render(/*#__PURE__*/(0, _react.createElement)(_MessageNotification.MessageNotification, {
31
+ title: '알림 제목',
32
+ ...props
33
+ }));
34
+ });
35
+ mounted.push({
36
+ root,
37
+ container
38
+ });
39
+ return container;
40
+ }
41
+ (0, _vitest.afterEach)(() => {
42
+ for (const {
43
+ root,
44
+ container
45
+ } of mounted.splice(0)) {
46
+ (0, _testUtils.act)(() => {
47
+ root.unmount();
48
+ });
49
+ container.remove();
50
+ }
51
+ });
52
+ (0, _vitest.describe)('MessageNotification (React) — DOM 구조 계약', () => {
53
+ (0, _vitest.it)('U-1: __footer-container가 __actions-container의 자식으로 렌더된다', () => {
54
+ // Given: 다시 보지 않기·닫기 핸들러가 주어지고
55
+ // When: MessageNotification을 렌더하면
56
+ const container = renderMessageNotification({
57
+ onHidePermanently: _vitest.vi.fn(),
58
+ onClose: _vitest.vi.fn()
59
+ });
60
+ // Then: footer-container는 actions-container의 직계 자식이다 (content의 직계 자식이 아니다)
61
+ const actionsContainer = container.querySelector(SELECTOR.ACTIONS_CONTAINER);
62
+ const footerContainer = container.querySelector(SELECTOR.FOOTER_CONTAINER);
63
+ (0, _vitest.expect)(actionsContainer).not.toBeNull();
64
+ (0, _vitest.expect)(footerContainer).not.toBeNull();
65
+ (0, _vitest.expect)(footerContainer?.parentElement).toBe(actionsContainer);
66
+ });
67
+ (0, _vitest.it)('U-2: actions 지정 시 __actions와 __footer-container가 __actions-container 안에 순서대로 공존한다', () => {
68
+ // Given: 액션 버튼 2개와 다시 보지 않기·닫기 핸들러가 주어지고
69
+ const actions = [{
70
+ label: '조회하기',
71
+ hierarchy: 'text',
72
+ onClick: _vitest.vi.fn()
73
+ }, {
74
+ label: '포인트 충전',
75
+ hierarchy: 'text-gray',
76
+ onClick: _vitest.vi.fn()
77
+ }];
78
+ // When: 렌더하면
79
+ const container = renderMessageNotification({
80
+ actions,
81
+ onHidePermanently: _vitest.vi.fn(),
82
+ onClose: _vitest.vi.fn()
83
+ });
84
+ // Then: actions-container의 자식은 [actions, footer-container] 순서다
85
+ const actionsContainer = container.querySelector(SELECTOR.ACTIONS_CONTAINER);
86
+ const actionsGroup = container.querySelector(SELECTOR.ACTIONS);
87
+ const footerContainer = container.querySelector(SELECTOR.FOOTER_CONTAINER);
88
+ (0, _vitest.expect)(actionsContainer).not.toBeNull();
89
+ (0, _vitest.expect)(Array.from(actionsContainer?.children ?? [])).toEqual([actionsGroup, footerContainer]);
90
+ });
91
+ (0, _vitest.it)('U-3: onHidePermanently·onClose·actions 미지정 시 빈 컨테이너가 남지 않는다', () => {
92
+ // Given/When: 우측 그룹에 표시할 요소가 하나도 없는 상태로 렌더하면
93
+ const container = renderMessageNotification();
94
+ // Then: actions-container도 footer-container도 렌더되지 않는다
95
+ (0, _vitest.expect)(container.querySelector(SELECTOR.ACTIONS_CONTAINER)).toBeNull();
96
+ (0, _vitest.expect)(container.querySelector(SELECTOR.FOOTER_CONTAINER)).toBeNull();
97
+ // 본문 영역은 그대로 유지된다
98
+ (0, _vitest.expect)(container.querySelector(SELECTOR.CONTENT_WRAPPER)).not.toBeNull();
99
+ });
100
+ (0, _vitest.it)('U-3-a: actions만 지정하면 actions-container만 남고 footer-container는 없다', () => {
101
+ // Given/When: 액션 버튼만 있고 닫기 그룹이 없으면
102
+ const container = renderMessageNotification({
103
+ actions: [{
104
+ label: '조회하기',
105
+ hierarchy: 'text',
106
+ onClick: _vitest.vi.fn()
107
+ }]
108
+ });
109
+ // Then: actions-container는 존재하고 그 자식은 actions 그룹 하나뿐이다
110
+ const actionsContainer = container.querySelector(SELECTOR.ACTIONS_CONTAINER);
111
+ const actionsGroup = container.querySelector(SELECTOR.ACTIONS);
112
+ (0, _vitest.expect)(actionsContainer).not.toBeNull();
113
+ (0, _vitest.expect)(container.querySelector(SELECTOR.FOOTER_CONTAINER)).toBeNull();
114
+ (0, _vitest.expect)(Array.from(actionsContainer?.children ?? [])).toEqual([actionsGroup]);
115
+ });
116
+ (0, _vitest.it)('U-3-b: onClose만 지정하면 footer-container에 닫기 버튼만 담긴다', () => {
117
+ // Given/When: 닫기 핸들러만 지정하면
118
+ const container = renderMessageNotification({
119
+ onClose: _vitest.vi.fn()
120
+ });
121
+ // Then: footer-container에는 닫기 버튼 1개만 존재하고 hide-link는 없다
122
+ const footerContainer = container.querySelector(SELECTOR.FOOTER_CONTAINER);
123
+ (0, _vitest.expect)(footerContainer?.children).toHaveLength(1);
124
+ (0, _vitest.expect)(footerContainer?.querySelector(SELECTOR.CLOSE_BUTTON)).not.toBeNull();
125
+ (0, _vitest.expect)(footerContainer?.querySelector(SELECTOR.HIDE_LINK)).toBeNull();
126
+ });
127
+ (0, _vitest.it)('U-4: FeaturedIcon이 md 사이즈(40px 원 / 20px 아이콘)로 렌더된다', () => {
128
+ // Given/When: error 색상으로 렌더하면
129
+ const container = renderMessageNotification({
130
+ color: 'error'
131
+ });
132
+ // Then: featured-icon은 --md 수식자를 가지며 내부 SVG는 20px다
133
+ const icon = container.querySelector(SELECTOR.ICON);
134
+ (0, _vitest.expect)(icon).not.toBeNull();
135
+ (0, _vitest.expect)(icon?.classList.contains('ncua-featured-icon--md')).toBe(true);
136
+ (0, _vitest.expect)(icon?.classList.contains('ncua-featured-icon--lg')).toBe(false);
137
+ (0, _vitest.expect)(icon?.querySelector('svg')?.getAttribute('width')).toBe('20');
138
+ });
139
+ (0, _vitest.it)('hide-link는 구현별 수식자(--link/--text) 없이 공통 클래스 2개만 갖는다', () => {
140
+ // Given/When: 다시 보지 않기 핸들러를 지정해 렌더하면
141
+ const container = renderMessageNotification({
142
+ onHidePermanently: _vitest.vi.fn()
143
+ });
144
+ // Then: SCSS 미정의 수식자 --link에 의존하지 않는다
145
+ const hideLink = container.querySelector(SELECTOR.HIDE_LINK);
146
+ (0, _vitest.expect)(hideLink?.className.split(' ').filter(Boolean).sort()).toEqual(['ncua-message-notification__hide-link', 'ncua-notification__action-button']);
147
+ });
148
+ });
149
+ (0, _vitest.describe)('MessageNotification (React) — 공개 API 회귀 방지', () => {
150
+ (0, _vitest.it)('지원하지 않는 color("info")는 neutral로 폴백한다', () => {
151
+ // Given/When: message 타입이 지원하지 않는 info 색상을 넘기면
152
+ const container = renderMessageNotification({
153
+ color: 'info'
154
+ });
155
+ // Then: neutral 수식자로 렌더되고 info 수식자는 없다
156
+ const root = container.querySelector(SELECTOR.ROOT);
157
+ (0, _vitest.expect)(root?.classList.contains('ncua-message-notification--neutral')).toBe(true);
158
+ (0, _vitest.expect)(root?.classList.contains('ncua-message-notification--info')).toBe(false);
159
+ });
160
+ (0, _vitest.it)('닫기/다시 보지 않기 클릭 시 핸들러가 호출되고 접근성 속성이 유지된다', () => {
161
+ // Given: 핸들러를 지정해 렌더하고
162
+ const onClose = _vitest.vi.fn();
163
+ const onHidePermanently = _vitest.vi.fn();
164
+ const container = renderMessageNotification({
165
+ onClose,
166
+ onHidePermanently
167
+ });
168
+ // When: 각 버튼을 클릭하면
169
+ (0, _testUtils.act)(() => {
170
+ container.querySelector(SELECTOR.CLOSE_BUTTON)?.click();
171
+ container.querySelector(SELECTOR.HIDE_LINK)?.click();
172
+ });
173
+ // Then: 핸들러가 각각 1회 호출되고 role/aria-label이 유지된다
174
+ (0, _vitest.expect)(onClose).toHaveBeenCalledTimes(1);
175
+ (0, _vitest.expect)(onHidePermanently).toHaveBeenCalledTimes(1);
176
+ (0, _vitest.expect)(container.querySelector(SELECTOR.ROOT)?.getAttribute('role')).toBe('alert');
177
+ (0, _vitest.expect)(container.querySelector(SELECTOR.CLOSE_BUTTON)?.getAttribute('aria-label')).toBe('알림 닫기');
178
+ });
179
+ });
180
+ (0, _vitest.describe)('MessageNotification (React) — 액션 버튼 색상(hierarchy)', () => {
181
+ const SIZE_MODIFIER = /^ncua-btn--(xs|sm|md|lg|xl)$/;
182
+ /** 사이즈를 제외한 `ncua-btn--*` 수식자 = 색상(hierarchy) 수식자. */
183
+ function buttonHierarchyModifiers(root) {
184
+ return Array.from(root.querySelectorAll('.ncua-btn')).map(button => Array.from(button.classList).find(name => name.startsWith('ncua-btn--') && !SIZE_MODIFIER.test(name)) ?? '');
185
+ }
186
+ (0, _vitest.it)('Figma TO-BE 조합(secondary-gray / secondary)을 ncua-btn 수식자로 반영한다', () => {
187
+ // Given/When: 조회하기 secondary-gray, 포인트 충전 secondary로 렌더하면
188
+ const container = renderMessageNotification({
189
+ actions: [{
190
+ label: '조회하기',
191
+ hierarchy: 'secondary-gray',
192
+ onClick: _vitest.vi.fn()
193
+ }, {
194
+ label: '포인트 충전',
195
+ hierarchy: 'secondary',
196
+ onClick: _vitest.vi.fn()
197
+ }]
198
+ });
199
+ // Then: ButtonTheme와 동일한 수식자가 순서대로 붙는다
200
+ (0, _vitest.expect)(buttonHierarchyModifiers(container)).toEqual(['ncua-btn--secondary-gray', 'ncua-btn--secondary']);
201
+ });
202
+ (0, _vitest.it)('text / text-gray도 계속 지원한다', () => {
203
+ // Given/When: 기존 어휘로 렌더하면
204
+ const container = renderMessageNotification({
205
+ actions: [{
206
+ label: '조회하기',
207
+ hierarchy: 'text',
208
+ onClick: _vitest.vi.fn()
209
+ }, {
210
+ label: '포인트 충전',
211
+ hierarchy: 'text-gray',
212
+ onClick: _vitest.vi.fn()
213
+ }]
214
+ });
215
+ // Then: 기존 수식자가 그대로 유지된다 (회귀 방지)
216
+ (0, _vitest.expect)(buttonHierarchyModifiers(container)).toEqual(['ncua-btn--text', 'ncua-btn--text-gray']);
217
+ });
218
+ (0, _vitest.it)('hierarchy를 생략하면 기본값 text로 렌더한다', () => {
219
+ // Given/When: 색상을 지정하지 않은 액션을 렌더하면
220
+ const container = renderMessageNotification({
221
+ actions: [{
222
+ label: '확인',
223
+ onClick: _vitest.vi.fn()
224
+ }]
225
+ });
226
+ // Then: Button의 기본값(secondary-gray)이 아니라 알림의 기본값 text가 적용된다
227
+ (0, _vitest.expect)(buttonHierarchyModifiers(container)).toEqual(['ncua-btn--text']);
228
+ });
229
+ (0, _vitest.it)('액션 버튼 클릭 시 onClick이 호출된다', () => {
230
+ // Given: secondary 색상 액션을 렌더하고
231
+ const onClick = _vitest.vi.fn();
232
+ const container = renderMessageNotification({
233
+ actions: [{
234
+ label: '포인트 충전',
235
+ hierarchy: 'secondary',
236
+ onClick
237
+ }]
238
+ });
239
+ // When: 버튼을 클릭하면
240
+ (0, _testUtils.act)(() => {
241
+ container.querySelector('.ncua-btn')?.click();
242
+ });
243
+ // Then: 핸들러가 1회 호출된다
244
+ (0, _vitest.expect)(onClick).toHaveBeenCalledTimes(1);
245
+ });
246
+ });
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+
3
+ var _react = require("react");
4
+ var _client = require("react-dom/client");
5
+ var _testUtils = require("react-dom/test-utils");
6
+ var _vitest = require("vitest");
7
+ var _FullWidthNotification = require("../FullWidthNotification");
8
+ var _MessageNotification = require("../MessageNotification");
9
+ var _Notification = require("../Notification");
10
+ // @vitest-environment jsdom
11
+
12
+ // React 18에서 act() 사용 시 필요한 플래그 (미설정 시 console.error 경고 발생)
13
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
14
+ const SELECTOR = {
15
+ /** message 타입 — 고도몰이 textContent 덮어쓰기에 쓰는 하위호환 계약 선택자. */
16
+ MESSAGE_HIDE_LINK: '.ncua-message-notification__hide-link',
17
+ /** full-width 타입 — 다시보지 않기 버튼. */
18
+ FULL_WIDTH_LINK: '.ncua-full-width-notification__link',
19
+ ACTION_BUTTON: '.ncua-notification__action-button'
20
+ };
21
+ /** 각 타입의 현행 기본 문구. 표기 통일은 별도 결정 사항이므로 서로 다른 값을 그대로 유지한다. */
22
+ const DEFAULT_LABEL = {
23
+ MESSAGE: '다시 보지 않기',
24
+ FULL_WIDTH: '다시보지 않기'
25
+ };
26
+ const mounted = [];
27
+ function render(element) {
28
+ const container = document.createElement('div');
29
+ document.body.appendChild(container);
30
+ const root = (0, _client.createRoot)(container);
31
+ (0, _testUtils.act)(() => {
32
+ root.render(element);
33
+ });
34
+ mounted.push({
35
+ root,
36
+ container
37
+ });
38
+ return container;
39
+ }
40
+ function renderMessage() {
41
+ let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
42
+ return render(/*#__PURE__*/(0, _react.createElement)(_MessageNotification.MessageNotification, {
43
+ title: '알림 제목',
44
+ ...props
45
+ }));
46
+ }
47
+ function renderFullWidth() {
48
+ let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
49
+ return render(/*#__PURE__*/(0, _react.createElement)(_FullWidthNotification.FullWidthNotification, {
50
+ title: '알림 제목',
51
+ ...props
52
+ }));
53
+ }
54
+ function renderNotification() {
55
+ let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
56
+ return render(/*#__PURE__*/(0, _react.createElement)(_Notification.Notification, {
57
+ title: '알림 제목',
58
+ ...props
59
+ }));
60
+ }
61
+ (0, _vitest.afterEach)(() => {
62
+ for (const {
63
+ root,
64
+ container
65
+ } of mounted.splice(0)) {
66
+ (0, _testUtils.act)(() => {
67
+ root.unmount();
68
+ });
69
+ container.remove();
70
+ }
71
+ });
72
+ (0, _vitest.describe)('hidePermanentlyLabel (React) — message 타입', () => {
73
+ (0, _vitest.it)('R-1: prop 미지정 시 기본 문구 "다시 보지 않기"가 렌더된다', () => {
74
+ // Given: hidePermanentlyLabel 없이 onHidePermanently만 주어지고
75
+ // When: MessageNotification을 렌더하면
76
+ const container = renderMessage({
77
+ onHidePermanently: _vitest.vi.fn()
78
+ });
79
+ // Then: 현행 기본 문구가 그대로 노출된다 (시각적 변화 0)
80
+ const hideLink = container.querySelector(SELECTOR.MESSAGE_HIDE_LINK);
81
+ (0, _vitest.expect)(hideLink?.textContent).toBe(DEFAULT_LABEL.MESSAGE);
82
+ });
83
+ (0, _vitest.it)('R-2: prop 지정 시 그 값이 렌더된다', () => {
84
+ // Given: 커스텀 라벨이 주어지고
85
+ // When: 렌더하면
86
+ const container = renderMessage({
87
+ onHidePermanently: _vitest.vi.fn(),
88
+ hidePermanentlyLabel: '30일간 보지 않기'
89
+ });
90
+ // Then: 하드코딩 문구 대신 전달한 값이 노출된다
91
+ const hideLink = container.querySelector(SELECTOR.MESSAGE_HIDE_LINK);
92
+ (0, _vitest.expect)(hideLink?.textContent).toBe('30일간 보지 않기');
93
+ });
94
+ (0, _vitest.it)('R-3: ReactNode 라벨을 받아 엘리먼트로 렌더한다', () => {
95
+ // Given: 문자열이 아닌 ReactNode 라벨이 주어지고
96
+ const label = /*#__PURE__*/(0, _react.createElement)('span', {
97
+ className: 'custom-hide-label'
98
+ }, '커스텀 노드');
99
+ // When: 렌더하면
100
+ const container = renderMessage({
101
+ onHidePermanently: _vitest.vi.fn(),
102
+ hidePermanentlyLabel: label
103
+ });
104
+ // Then: 노드가 hide-link 내부에 그대로 마운트된다
105
+ const hideLink = container.querySelector(SELECTOR.MESSAGE_HIDE_LINK);
106
+ (0, _vitest.expect)(hideLink?.querySelector('.custom-hide-label')?.textContent).toBe('커스텀 노드');
107
+ });
108
+ (0, _vitest.it)('R-4: 라벨을 바꿔도 __hide-link 클래스 계약이 유지된다', () => {
109
+ // Given: 커스텀 라벨이 주어지고
110
+ // When: 렌더하면
111
+ const container = renderMessage({
112
+ onHidePermanently: _vitest.vi.fn(),
113
+ hidePermanentlyLabel: '커스텀'
114
+ });
115
+ // Then: 고도몰의 textContent 덮어쓰기가 계속 동작하도록 기존 클래스가 모두 남는다
116
+ const hideLink = container.querySelector(SELECTOR.MESSAGE_HIDE_LINK);
117
+ (0, _vitest.expect)(hideLink).not.toBeNull();
118
+ (0, _vitest.expect)(hideLink?.classList.contains('ncua-notification__action-button')).toBe(true);
119
+ (0, _vitest.expect)(hideLink?.classList.contains('ncua-message-notification__hide-link')).toBe(true);
120
+ });
121
+ (0, _vitest.it)('R-5: onHidePermanently 없이 라벨만 주면 버튼이 렌더되지 않는다', () => {
122
+ // Given: 핸들러 없이 라벨만 주어지고
123
+ // When: 렌더하면
124
+ const container = renderMessage({
125
+ hidePermanentlyLabel: '커스텀'
126
+ });
127
+ // Then: 라벨만으로는 버튼이 생기지 않는다 (핸들러가 렌더 조건)
128
+ (0, _vitest.expect)(container.querySelector(SELECTOR.MESSAGE_HIDE_LINK)).toBeNull();
129
+ });
130
+ (0, _vitest.it)('R-6: 라벨이 DOM 속성으로 새어나가지 않는다', () => {
131
+ // Given: 커스텀 라벨이 주어지고
132
+ // When: 렌더하면
133
+ const container = renderMessage({
134
+ onHidePermanently: _vitest.vi.fn(),
135
+ hidePermanentlyLabel: '커스텀'
136
+ });
137
+ // Then: rest 스프레드를 타고 루트 div의 속성으로 흘러들지 않는다
138
+ const root = container.querySelector('.ncua-message-notification');
139
+ (0, _vitest.expect)(root?.hasAttribute('hidepermanentlylabel')).toBe(false);
140
+ });
141
+ });
142
+ (0, _vitest.describe)('hidePermanentlyLabel (React) — full-width 타입', () => {
143
+ (0, _vitest.it)('R-7: prop 미지정 시 기본 문구 "다시보지 않기"가 렌더된다', () => {
144
+ // Given: hidePermanentlyLabel 없이 onHidePermanently만 주어지고
145
+ // When: FullWidthNotification을 렌더하면
146
+ const container = renderFullWidth({
147
+ onHidePermanently: _vitest.vi.fn()
148
+ });
149
+ // Then: message 타입과 다른 현행 문구가 그대로 유지된다 (표기 통일은 별도 결정)
150
+ const hideButton = container.querySelector(SELECTOR.FULL_WIDTH_LINK);
151
+ (0, _vitest.expect)(hideButton?.textContent).toBe(DEFAULT_LABEL.FULL_WIDTH);
152
+ });
153
+ (0, _vitest.it)('R-8: prop 지정 시 그 값이 렌더된다', () => {
154
+ // Given: 커스텀 라벨이 주어지고
155
+ // When: 렌더하면
156
+ const container = renderFullWidth({
157
+ onHidePermanently: _vitest.vi.fn(),
158
+ hidePermanentlyLabel: '오늘 하루 보지 않기'
159
+ });
160
+ // Then: 전달한 값이 노출된다
161
+ const hideButton = container.querySelector(SELECTOR.FULL_WIDTH_LINK);
162
+ (0, _vitest.expect)(hideButton?.textContent).toBe('오늘 하루 보지 않기');
163
+ });
164
+ (0, _vitest.it)('R-9: 라벨을 바꿔도 기존 클래스 계약이 유지된다', () => {
165
+ // Given: 커스텀 라벨이 주어지고
166
+ // When: 렌더하면
167
+ const container = renderFullWidth({
168
+ onHidePermanently: _vitest.vi.fn(),
169
+ hidePermanentlyLabel: '커스텀'
170
+ });
171
+ // Then: 액션 버튼 클래스가 그대로 남는다
172
+ const hideButton = container.querySelector(SELECTOR.FULL_WIDTH_LINK);
173
+ (0, _vitest.expect)(hideButton?.classList.contains('ncua-notification__action-button')).toBe(true);
174
+ (0, _vitest.expect)(hideButton?.classList.contains('ncua-notification__action-button--text')).toBe(true);
175
+ });
176
+ (0, _vitest.it)('R-10: onHidePermanently 없이 라벨만 주면 버튼이 렌더되지 않는다', () => {
177
+ // Given: 핸들러 없이 라벨만 주어지고
178
+ // When: 렌더하면
179
+ const container = renderFullWidth({
180
+ hidePermanentlyLabel: '커스텀'
181
+ });
182
+ // Then: 액션 버튼이 생기지 않는다
183
+ (0, _vitest.expect)(container.querySelector(SELECTOR.ACTION_BUTTON)).toBeNull();
184
+ });
185
+ });
186
+ (0, _vitest.describe)('hidePermanentlyLabel (React) — Notification 공유 Props 경유', () => {
187
+ (0, _vitest.it)('R-11: type="message"에서 공유 Props의 라벨이 전달된다', () => {
188
+ // Given: 공유 Notification props로 라벨이 주어지고
189
+ // When: type="message"로 렌더하면
190
+ const container = renderNotification({
191
+ type: 'message',
192
+ onHidePermanently: _vitest.vi.fn(),
193
+ hidePermanentlyLabel: '공유 프롭 라벨'
194
+ });
195
+ // Then: message 구현까지 라벨이 도달한다
196
+ (0, _vitest.expect)(container.querySelector(SELECTOR.MESSAGE_HIDE_LINK)?.textContent).toBe('공유 프롭 라벨');
197
+ });
198
+ (0, _vitest.it)('R-12: type="full-width"에서 공유 Props의 라벨이 전달된다', () => {
199
+ // Given: 공유 Notification props로 라벨이 주어지고
200
+ // When: type="full-width"로 렌더하면
201
+ const container = renderNotification({
202
+ type: 'full-width',
203
+ onHidePermanently: _vitest.vi.fn(),
204
+ hidePermanentlyLabel: '공유 프롭 라벨'
205
+ });
206
+ // Then: full-width 구현까지 라벨이 도달한다
207
+ (0, _vitest.expect)(container.querySelector(SELECTOR.FULL_WIDTH_LINK)?.textContent).toBe('공유 프롭 라벨');
208
+ });
209
+ });