@ncds/ui-admin 1.8.21-alpha.5 → 1.8.21-alpha.7

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 (71) hide show
  1. package/dist/cjs/assets/scripts/notification/FullWidthNotification.js +17 -6
  2. package/dist/cjs/assets/scripts/notification/MessageNotification.js +4 -2
  3. package/dist/cjs/assets/scripts/notification/messageTemplate.js +37 -8
  4. package/dist/cjs/constant/notification.js +21 -0
  5. package/dist/cjs/src/components/forms-and-input/input-base/InputBase.js +20 -14
  6. package/dist/cjs/src/components/index.js +11 -0
  7. package/dist/cjs/src/components/overlays/notification/FullWidthNotification.js +3 -1
  8. package/dist/cjs/src/components/overlays/notification/MessageNotification.js +2 -0
  9. package/dist/cjs/src/components/overlays/notification/MessageNotificationParts.js +3 -1
  10. package/dist/cjs/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +209 -0
  11. package/dist/cjs/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +283 -0
  12. package/dist/cjs/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +566 -0
  13. package/dist/cjs/src/components/overlays/postcode-search-modal/index.js +16 -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 +4 -2
  17. package/dist/esm/assets/scripts/notification/messageTemplate.js +37 -8
  18. package/dist/esm/constant/notification.js +15 -0
  19. package/dist/esm/src/components/forms-and-input/input-base/InputBase.js +20 -14
  20. package/dist/esm/src/components/index.js +1 -0
  21. package/dist/esm/src/components/overlays/notification/FullWidthNotification.js +3 -1
  22. package/dist/esm/src/components/overlays/notification/MessageNotification.js +2 -0
  23. package/dist/esm/src/components/overlays/notification/MessageNotificationParts.js +3 -1
  24. package/dist/esm/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +206 -0
  25. package/dist/esm/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +276 -0
  26. package/dist/esm/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +563 -0
  27. package/dist/esm/src/components/overlays/postcode-search-modal/index.js +1 -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.js +2 -1
  32. package/dist/temp/assets/scripts/notification/const/types.d.ts +10 -0
  33. package/dist/temp/assets/scripts/notification/messageTemplate.d.ts +1 -0
  34. package/dist/temp/assets/scripts/notification/messageTemplate.js +18 -8
  35. package/dist/temp/constant/notification.d.ts +15 -0
  36. package/dist/temp/constant/notification.js +15 -0
  37. package/dist/temp/src/components/forms-and-input/input-base/InputBase.js +18 -10
  38. package/dist/temp/src/components/index.d.ts +1 -0
  39. package/dist/temp/src/components/index.js +1 -0
  40. package/dist/temp/src/components/overlays/notification/FullWidthNotification.d.ts +6 -0
  41. package/dist/temp/src/components/overlays/notification/FullWidthNotification.js +3 -2
  42. package/dist/temp/src/components/overlays/notification/MessageNotification.d.ts +6 -0
  43. package/dist/temp/src/components/overlays/notification/MessageNotification.js +2 -2
  44. package/dist/temp/src/components/overlays/notification/MessageNotificationParts.d.ts +2 -1
  45. package/dist/temp/src/components/overlays/notification/MessageNotificationParts.js +3 -2
  46. package/dist/temp/src/components/overlays/notification/Notification.d.ts +8 -0
  47. package/dist/temp/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.d.ts +1 -0
  48. package/dist/temp/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +160 -0
  49. package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +49 -0
  50. package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +118 -0
  51. package/dist/temp/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.d.ts +1 -0
  52. package/dist/temp/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +456 -0
  53. package/dist/temp/src/components/overlays/postcode-search-modal/index.d.ts +1 -0
  54. package/dist/temp/src/components/overlays/postcode-search-modal/index.js +1 -0
  55. package/dist/temp/src/generated/scoped-css.js +1 -1
  56. package/dist/types/assets/scripts/notification/FullWidthNotification.d.ts +6 -0
  57. package/dist/types/assets/scripts/notification/const/types.d.ts +10 -0
  58. package/dist/types/assets/scripts/notification/messageTemplate.d.ts +1 -0
  59. package/dist/types/constant/notification.d.ts +15 -0
  60. package/dist/types/src/components/index.d.ts +1 -0
  61. package/dist/types/src/components/overlays/notification/FullWidthNotification.d.ts +6 -0
  62. package/dist/types/src/components/overlays/notification/MessageNotification.d.ts +6 -0
  63. package/dist/types/src/components/overlays/notification/MessageNotificationParts.d.ts +2 -1
  64. package/dist/types/src/components/overlays/notification/Notification.d.ts +8 -0
  65. package/dist/types/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.d.ts +1 -0
  66. package/dist/types/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +49 -0
  67. package/dist/types/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.d.ts +1 -0
  68. package/dist/types/src/components/overlays/postcode-search-modal/index.d.ts +1 -0
  69. package/dist/ui-admin/assets/styles/style.css +188 -13
  70. package/dist/ui-admin/assets/styles/style.scoped.css +188 -13
  71. package/package.json +1 -1
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.FullWidthNotification = void 0;
7
+ var _notification = require("../../../constant/notification");
7
8
  var _sanitize = require("../utils/sanitize");
8
9
  var _const = require("./const");
9
10
  var _utils = require("./utils");
@@ -30,7 +31,8 @@ class FullWidthNotification {
30
31
  actions,
31
32
  onClose,
32
33
  supportTextLink,
33
- onHidePermanently
34
+ onHidePermanently,
35
+ hidePermanentlyLabel
34
36
  } = this.options;
35
37
  const wrapper = (0, _utils.createWrapperElement)(_const.CLASS_NAMES.FULL_WIDTH.BASE, color, className);
36
38
  const iconHtml = _const.FULL_WIDTH_ICON_MAP[color](_const.FULL_WIDTH_SIZES.ICON);
@@ -41,7 +43,8 @@ class FullWidthNotification {
41
43
  actions,
42
44
  onClose,
43
45
  supportTextLink,
44
- onHidePermanently
46
+ onHidePermanently,
47
+ hidePermanentlyLabel
45
48
  }));
46
49
  return wrapper;
47
50
  }
@@ -53,7 +56,8 @@ class FullWidthNotification {
53
56
  actions,
54
57
  onClose,
55
58
  supportTextLink,
56
- onHidePermanently
59
+ onHidePermanently,
60
+ hidePermanentlyLabel
57
61
  } = params;
58
62
  return `
59
63
  <div class="${_const.CLASS_NAMES.FULL_WIDTH.CONTAINER}">
@@ -67,18 +71,25 @@ class FullWidthNotification {
67
71
  </div>
68
72
  <div class="${_const.CLASS_NAMES.FULL_WIDTH.ACTIONS_CONTAINER}">
69
73
  ${actions && actions.length > 0 ? (0, _utils.renderActions)(actions, _const.CLASS_NAMES.FULL_WIDTH.ACTIONS) : ''}
70
- ${this.renderHidePermanentlyButton(onHidePermanently)}
74
+ ${this.renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel)}
71
75
  ${this.renderCloseButton(onClose)}
72
76
  </div>
73
77
  </div>
74
78
  </div>
75
79
  `;
76
80
  }
77
- renderHidePermanentlyButton(onHidePermanently) {
81
+ /**
82
+ * 다시보지 않기 버튼.
83
+ *
84
+ * `label`은 이 함수의 반환값이 `wrapper.innerHTML`에 들어가기 전에 `sanitizeHtml`로
85
+ * 처리되므로 별도 이스케이프하지 않는다 — `renderSupportingText`와 같은 정책이다.
86
+ */
87
+ renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel) {
78
88
  if (!onHidePermanently) return '';
89
+ const label = hidePermanentlyLabel ?? _notification.HIDE_PERMANENTLY_LABEL.fullWidth;
79
90
  return `
80
91
  <button type="button" class="ncua-notification__action-button ncua-notification__action-button--text ncua-full-width-notification__link" data-hide-permanently="true">
81
- 다시보지 않기
92
+ ${label}
82
93
  </button>
83
94
  `;
84
95
  }
@@ -31,7 +31,8 @@ class MessageNotification {
31
31
  className,
32
32
  actions,
33
33
  onClose,
34
- onHidePermanently
34
+ onHidePermanently,
35
+ hidePermanentlyLabel
35
36
  } = this.options;
36
37
  // message 타입은 neutral, error, warning, success 4가지 색상만 지원
37
38
  let actualColor = color;
@@ -60,7 +61,8 @@ class MessageNotification {
60
61
  supportingText,
61
62
  actions,
62
63
  onClose,
63
- onHidePermanently
64
+ onHidePermanently,
65
+ hidePermanentlyLabel
64
66
  }));
65
67
  // FeaturedIcon을 content-wrapper에 추가
66
68
  if (featuredIconElement) {
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.buildMessageTemplate = buildMessageTemplate;
7
+ var _notification = require("../../../constant/notification");
7
8
  var _const = require("./const");
8
9
  var _utils = require("./utils");
9
10
  /**
@@ -22,7 +23,8 @@ function buildMessageTemplate(params) {
22
23
  supportingText,
23
24
  actions,
24
25
  onClose,
25
- onHidePermanently
26
+ onHidePermanently,
27
+ hidePermanentlyLabel
26
28
  } = params;
27
29
  return `
28
30
  <div class="${_const.CLASS_NAMES.MESSAGE.CONTAINER}">
@@ -33,7 +35,12 @@ function buildMessageTemplate(params) {
33
35
  ${(0, _utils.renderSupportingText)(supportingText, _const.CLASS_NAMES.MESSAGE.SUPPORTING_TEXT)}
34
36
  </div>
35
37
  </div>
36
- ${renderActionsContainer(actions, onClose, onHidePermanently)}
38
+ ${renderActionsContainer({
39
+ actions,
40
+ onClose,
41
+ onHidePermanently,
42
+ hidePermanentlyLabel
43
+ })}
37
44
  </div>
38
45
  </div>
39
46
  `;
@@ -43,27 +50,49 @@ function buildMessageTemplate(params) {
43
50
  * 이 컨테이너의 gap(12px)이 두 그룹 사이 간격을 만든다.
44
51
  * 표시할 요소가 하나도 없으면 빈 컨테이너를 남기지 않는다.
45
52
  */
46
- function renderActionsContainer(actions, onClose, onHidePermanently) {
53
+ function renderActionsContainer(params) {
54
+ const {
55
+ actions,
56
+ onClose,
57
+ onHidePermanently,
58
+ hidePermanentlyLabel
59
+ } = params;
47
60
  const actionsHtml = (0, _utils.renderActions)(actions ?? [], _const.CLASS_NAMES.MESSAGE.ACTIONS, ACTION_BUTTON_SIZE);
48
- const footerHtml = renderFooterContainer(onClose, onHidePermanently);
61
+ const footerHtml = renderFooterContainer({
62
+ onClose,
63
+ onHidePermanently,
64
+ hidePermanentlyLabel
65
+ });
49
66
  if (!actionsHtml && !footerHtml) return '';
50
67
  return `<div class="${_const.CLASS_NAMES.MESSAGE.ACTIONS_CONTAINER}">${actionsHtml}${footerHtml}</div>`;
51
68
  }
52
69
  /** 다시 보지 않기 + 닫기 그룹. 전용 래퍼를 쓰는 이유는 `CLASS_NAMES.MESSAGE.FOOTER_CONTAINER` 참조. */
53
- function renderFooterContainer(onClose, onHidePermanently) {
70
+ function renderFooterContainer(params) {
71
+ const {
72
+ onClose,
73
+ onHidePermanently,
74
+ hidePermanentlyLabel
75
+ } = params;
54
76
  if (!onHidePermanently && !onClose) return '';
55
77
  return `
56
78
  <div class="${_const.CLASS_NAMES.MESSAGE.FOOTER_CONTAINER}">
57
- ${renderHidePermanentlyButton(onHidePermanently)}
79
+ ${renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel)}
58
80
  ${renderCloseButton(onClose)}
59
81
  </div>
60
82
  `;
61
83
  }
62
- function renderHidePermanentlyButton(onHidePermanently) {
84
+ /**
85
+ * 다시 보지 않기 버튼.
86
+ *
87
+ * `label`은 이 함수의 반환값이 `wrapper.innerHTML`에 들어가기 전에 `sanitizeHtml`로
88
+ * 처리되므로 별도 이스케이프하지 않는다 — `renderSupportingText`와 같은 정책이다.
89
+ */
90
+ function renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel) {
63
91
  if (!onHidePermanently) return '';
92
+ const label = hidePermanentlyLabel ?? _notification.HIDE_PERMANENTLY_LABEL.message;
64
93
  return `
65
94
  <button type="button" class="${_const.CLASS_NAMES.COMMON.ACTION_BUTTON} ${_const.CLASS_NAMES.MESSAGE.HIDE_LINK}" data-hide-permanently="true">
66
- 다시 보지 않기
95
+ ${label}
67
96
  </button>
68
97
  `;
69
98
  }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.HIDE_PERMANENTLY_LABEL = void 0;
7
+ /**
8
+ * 다시보지 않기 버튼의 타입별 기본 문구.
9
+ *
10
+ * `hidePermanentlyLabel`을 지정하지 않았을 때 쓰인다.
11
+ * React(`src/components/overlays/notification/`)와 Vanilla(`assets/scripts/notification/`)
12
+ * 두 구현이 이 상수를 함께 참조한다 — 문구가 하드코딩으로 흩어져 서로 어긋나던 문제(#460)를
13
+ * 막기 위한 단일 출처다.
14
+ *
15
+ * `message`와 `fullWidth`의 띄어쓰기가 다른 것은 오타가 아니라 현행 유지다.
16
+ * 표기 통일은 별도 결정 사항이며, 결정되면 이 파일 한 곳만 고치면 된다.
17
+ */
18
+ const HIDE_PERMANENTLY_LABEL = exports.HIDE_PERMANENTLY_LABEL = {
19
+ message: '다시 보지 않기',
20
+ fullWidth: '다시보지 않기'
21
+ };
@@ -39,26 +39,27 @@ const InputBase = exports.InputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref,
39
39
  ...props
40
40
  } = _ref;
41
41
  const inputRef = (0, _react.useRef)(null);
42
- const [textCount, setTextCount] = (0, _react.useState)(0);
43
- const [hasValue, setHasValue] = (0, _react.useState)(false);
44
- // 유무는 DOM에서 읽는다 — props.value 판정하면 uncontrolled Input에서 항상 빈 값으로 보인다.
45
- // biome-ignore lint/correctness/useExhaustiveDependencies: controlled 값이 프로그래밍적으로 바뀔 때는 input 이벤트가 나지 않아 props.value로 재동기화해야 한다
42
+ // controlled 인풋은 props.value 길이를 알 수 있으므로 상태를 두지 않는다.
43
+ // 입력마다 setState 하면 조합 중(한글 IME)에 리렌더가 끼어들어 조합이 끊긴다.
44
+ const isControlled = props.value !== undefined;
45
+ const [uncontrolledLength, setUncontrolledLength] = (0, _react.useState)(0);
46
+ const valueLength = isControlled ? String(props.value ?? '').length : uncontrolledLength;
47
+ const hasValue = valueLength > 0;
48
+ const textCount = valueLength;
49
+ // uncontrolled 인풋만 DOM 값을 읽어 따라간다 — props.value 가 없어 길이를 알 방법이 이것뿐이다.
46
50
  (0, _react.useEffect)(() => {
51
+ if (isControlled) return;
47
52
  const input = inputRef.current;
48
53
  if (!input) return;
49
54
  const syncFromInput = () => {
50
- const {
51
- length
52
- } = input.value;
53
- setHasValue(length > 0);
54
- setTextCount(length);
55
+ setUncontrolledLength(input.value.length);
55
56
  };
56
57
  syncFromInput();
57
58
  input.addEventListener('input', syncFromInput);
58
59
  return () => {
59
60
  input.removeEventListener('input', syncFromInput);
60
61
  };
61
- }, [props.value]);
62
+ }, [isControlled]);
62
63
  const renderOutsideSlot = (slot, position) => {
63
64
  if (slot.type === 'custom' && slot.placement !== 'outside') {
64
65
  return null;
@@ -72,10 +73,15 @@ const InputBase = exports.InputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref,
72
73
  children: slot.label
73
74
  });
74
75
  case 'text':
75
- return (0, _jsxRuntime.jsx)("span", {
76
- className: (0, _classnames.default)(`ncua-input__${position}-text`, `ncua-input__${position}-text--${size}`),
77
- children: slot.text
78
- });
76
+ {
77
+ // 클래스명은 리터럴로 쓴다 — MCP 의 BEM 추출기가 `ncua-input__${position}-text` 형태의
78
+ // 템플릿 리터럴에서는 `ncua-input__` 조각만 읽어 완성된 클래스를 잡지 못한다.
79
+ const textClass = position === 'leading' ? 'ncua-input__leading-text' : 'ncua-input__trailing-text';
80
+ return (0, _jsxRuntime.jsx)("span", {
81
+ className: (0, _classnames.default)(textClass, `${textClass}--${size}`),
82
+ children: slot.text
83
+ });
84
+ }
79
85
  case 'custom':
80
86
  return slot.children;
81
87
  default:
@@ -498,6 +498,17 @@ Object.keys(_notification).forEach(function (key) {
498
498
  }
499
499
  });
500
500
  });
501
+ var _postcodeSearchModal = require("./overlays/postcode-search-modal");
502
+ Object.keys(_postcodeSearchModal).forEach(function (key) {
503
+ if (key === "default" || key === "__esModule") return;
504
+ if (key in exports && exports[key] === _postcodeSearchModal[key]) return;
505
+ Object.defineProperty(exports, key, {
506
+ enumerable: true,
507
+ get: function () {
508
+ return _postcodeSearchModal[key];
509
+ }
510
+ });
511
+ });
501
512
  var _tooltip = require("./overlays/tooltip");
502
513
  Object.keys(_tooltip).forEach(function (key) {
503
514
  if (key === "default" || key === "__esModule") return;
@@ -9,6 +9,7 @@ var _uiAdminIcon = require("@ncds/ui-admin-icon");
9
9
  var _classnames = _interopRequireDefault(require("classnames"));
10
10
  var _react = require("react");
11
11
  var _color = require("../../../../constant/color");
12
+ var _notification = require("../../../../constant/notification");
12
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
14
  const iconMap = {
14
15
  neutral: _uiAdminIcon.MessageChatSquare,
@@ -35,6 +36,7 @@ const FullWidthNotification = exports.FullWidthNotification = /*#__PURE__*/(0, _
35
36
  autoClose = 0,
36
37
  supportTextLink,
37
38
  onHidePermanently,
39
+ hidePermanentlyLabel = _notification.HIDE_PERMANENTLY_LABEL.fullWidth,
38
40
  ...rest
39
41
  } = _ref;
40
42
  const [shouldRemove, setShouldRemove] = (0, _react.useState)(false);
@@ -116,7 +118,7 @@ const FullWidthNotification = exports.FullWidthNotification = /*#__PURE__*/(0, _
116
118
  type: "button",
117
119
  className: (0, _classnames.default)('ncua-notification__action-button', 'ncua-notification__action-button--text', 'ncua-full-width-notification__link'),
118
120
  onClick: onHidePermanently,
119
- children: "\uB2E4\uC2DC\uBCF4\uC9C0 \uC54A\uAE30"
121
+ children: hidePermanentlyLabel
120
122
  }), onClose && (0, _jsxRuntime.jsx)("button", {
121
123
  type: "button",
122
124
  className: "ncua-full-width-notification__close-button",
@@ -33,6 +33,7 @@ const MessageNotification = exports.MessageNotification = /*#__PURE__*/(0, _reac
33
33
  className,
34
34
  actions,
35
35
  onHidePermanently,
36
+ hidePermanentlyLabel,
36
37
  ...rest
37
38
  } = _ref;
38
39
  // message 타입은 neutral, error, warning, success 4가지 색상만 지원
@@ -56,6 +57,7 @@ const MessageNotification = exports.MessageNotification = /*#__PURE__*/(0, _reac
56
57
  actions: actions,
57
58
  onClose: onClose,
58
59
  onHidePermanently: onHidePermanently,
60
+ hidePermanentlyLabel: hidePermanentlyLabel,
59
61
  closeIconColor: _color.COLOR[iconColorMap[validColor] || 'gray700']
60
62
  })]
61
63
  })
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.MessageNotificationContent = exports.MessageNotificationActions = void 0;
7
7
  var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _uiAdminIcon = require("@ncds/ui-admin-icon");
9
+ var _notification = require("../../../../constant/notification");
9
10
  var _button = require("../../action/button");
10
11
  var _FeaturedIcon = require("../../image-and-icons/featured-icon/FeaturedIcon");
11
12
  /** 아이콘 + 제목/서포팅 텍스트 영역. */
@@ -51,6 +52,7 @@ const MessageNotificationActions = _ref2 => {
51
52
  actions,
52
53
  onClose,
53
54
  onHidePermanently,
55
+ hidePermanentlyLabel = _notification.HIDE_PERMANENTLY_LABEL.message,
54
56
  closeIconColor
55
57
  } = _ref2;
56
58
  const hasActions = Boolean(actions?.length);
@@ -72,7 +74,7 @@ const MessageNotificationActions = _ref2 => {
72
74
  type: "button",
73
75
  className: "ncua-notification__action-button ncua-message-notification__hide-link",
74
76
  onClick: onHidePermanently,
75
- children: "\uB2E4\uC2DC \uBCF4\uC9C0 \uC54A\uAE30"
77
+ children: hidePermanentlyLabel
76
78
  }), onClose && (0, _jsxRuntime.jsx)("button", {
77
79
  type: "button",
78
80
  className: "ncua-message-notification__close-button",
@@ -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
+ });