@ncds/ui-admin 1.8.21-alpha.1 → 1.8.21-alpha.10
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.
- package/dist/cjs/assets/scripts/notification/FullWidthNotification.js +17 -6
- package/dist/cjs/assets/scripts/notification/MessageNotification.js +10 -48
- package/dist/cjs/assets/scripts/notification/const/classNames.js +3 -0
- package/dist/cjs/assets/scripts/notification/const/index.js +1 -1
- package/dist/cjs/assets/scripts/notification/const/sizes.js +4 -2
- package/dist/cjs/assets/scripts/notification/messageTemplate.js +106 -0
- package/dist/cjs/assets/scripts/notification/utils.js +40 -4
- package/dist/cjs/constant/notification.js +21 -0
- package/dist/cjs/src/components/forms-and-input/input-base/InputBase.js +30 -18
- package/dist/cjs/src/components/forms-and-input/input-base/__tests__/InputBase.test.js +116 -0
- package/dist/cjs/src/components/forms-and-input/password-input/PasswordInput.js +3 -3
- package/dist/cjs/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.js +59 -0
- package/dist/cjs/src/components/index.js +11 -0
- package/dist/cjs/src/components/layout/ncua-scope/NcuaScope.js +8 -2
- package/dist/cjs/src/components/navigation/pagination/Pagination.js +3 -1
- package/dist/cjs/src/components/navigation/pagination/__tests__/Pagination.test.js +74 -0
- package/dist/cjs/src/components/overlays/notification/FullWidthNotification.js +3 -1
- package/dist/cjs/src/components/overlays/notification/MessageNotification.js +14 -54
- package/dist/cjs/src/components/overlays/notification/MessageNotificationParts.js +92 -0
- package/dist/cjs/src/components/overlays/notification/__tests__/MessageNotification.test.js +246 -0
- package/dist/cjs/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +209 -0
- package/dist/cjs/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +259 -0
- package/dist/cjs/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.js +70 -0
- package/dist/cjs/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +623 -0
- package/dist/cjs/src/components/overlays/postcode-search-modal/index.js +16 -0
- package/dist/cjs/src/generated/scoped-css.js +1 -1
- package/dist/esm/assets/scripts/notification/FullWidthNotification.js +17 -6
- package/dist/esm/assets/scripts/notification/MessageNotification.js +12 -50
- package/dist/esm/assets/scripts/notification/const/classNames.js +3 -0
- package/dist/esm/assets/scripts/notification/const/index.js +3 -2
- package/dist/esm/assets/scripts/notification/const/sizes.js +4 -2
- package/dist/esm/assets/scripts/notification/messageTemplate.js +100 -0
- package/dist/esm/assets/scripts/notification/utils.js +39 -4
- package/dist/esm/constant/notification.js +15 -0
- package/dist/esm/src/components/forms-and-input/input-base/InputBase.js +30 -18
- package/dist/esm/src/components/forms-and-input/input-base/__tests__/InputBase.test.js +116 -0
- package/dist/esm/src/components/forms-and-input/password-input/PasswordInput.js +3 -3
- package/dist/esm/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.js +56 -0
- package/dist/esm/src/components/index.js +1 -0
- package/dist/esm/src/components/layout/ncua-scope/NcuaScope.js +8 -2
- package/dist/esm/src/components/navigation/pagination/Pagination.js +3 -1
- package/dist/esm/src/components/navigation/pagination/__tests__/Pagination.test.js +74 -0
- package/dist/esm/src/components/overlays/notification/FullWidthNotification.js +3 -1
- package/dist/esm/src/components/overlays/notification/MessageNotification.js +15 -55
- package/dist/esm/src/components/overlays/notification/MessageNotificationParts.js +85 -0
- package/dist/esm/src/components/overlays/notification/__tests__/MessageNotification.test.js +243 -0
- package/dist/esm/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +206 -0
- package/dist/esm/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +252 -0
- package/dist/esm/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.js +63 -0
- package/dist/esm/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +620 -0
- package/dist/esm/src/components/overlays/postcode-search-modal/index.js +1 -0
- package/dist/esm/src/generated/scoped-css.js +1 -1
- package/dist/temp/assets/scripts/notification/FullWidthNotification.d.ts +6 -0
- package/dist/temp/assets/scripts/notification/FullWidthNotification.js +14 -5
- package/dist/temp/assets/scripts/notification/MessageNotification.d.ts +0 -3
- package/dist/temp/assets/scripts/notification/MessageNotification.js +9 -44
- package/dist/temp/assets/scripts/notification/const/classNames.d.ts +1 -0
- package/dist/temp/assets/scripts/notification/const/classNames.js +3 -0
- package/dist/temp/assets/scripts/notification/const/index.d.ts +3 -3
- package/dist/temp/assets/scripts/notification/const/index.js +3 -2
- package/dist/temp/assets/scripts/notification/const/sizes.d.ts +2 -2
- package/dist/temp/assets/scripts/notification/const/sizes.js +4 -2
- package/dist/temp/assets/scripts/notification/const/types.d.ts +30 -2
- package/dist/temp/assets/scripts/notification/messageTemplate.d.ts +15 -0
- package/dist/temp/assets/scripts/notification/messageTemplate.js +79 -0
- package/dist/temp/assets/scripts/notification/utils.d.ts +19 -2
- package/dist/temp/assets/scripts/notification/utils.js +39 -5
- package/dist/temp/constant/notification.d.ts +15 -0
- package/dist/temp/constant/notification.js +15 -0
- package/dist/temp/src/components/forms-and-input/input-base/InputBase.d.ts +1 -1
- package/dist/temp/src/components/forms-and-input/input-base/InputBase.js +26 -14
- package/dist/temp/src/components/forms-and-input/input-base/__tests__/InputBase.test.js +79 -0
- package/dist/temp/src/components/forms-and-input/password-input/PasswordInput.js +2 -2
- package/dist/temp/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.d.ts +1 -0
- package/dist/temp/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.js +48 -0
- package/dist/temp/src/components/index.d.ts +1 -0
- package/dist/temp/src/components/index.js +1 -0
- package/dist/temp/src/components/layout/ncua-scope/NcuaScope.js +5 -1
- package/dist/temp/src/components/navigation/pagination/Pagination.js +3 -1
- package/dist/temp/src/components/navigation/pagination/__tests__/Pagination.test.js +74 -0
- package/dist/temp/src/components/overlays/notification/FullWidthNotification.d.ts +6 -0
- package/dist/temp/src/components/overlays/notification/FullWidthNotification.js +3 -2
- package/dist/temp/src/components/overlays/notification/MessageNotification.d.ts +6 -0
- package/dist/temp/src/components/overlays/notification/MessageNotification.js +4 -13
- package/dist/temp/src/components/overlays/notification/MessageNotificationParts.d.ts +28 -0
- package/dist/temp/src/components/overlays/notification/MessageNotificationParts.js +28 -0
- package/dist/temp/src/components/overlays/notification/Notification.d.ts +11 -2
- package/dist/temp/src/components/overlays/notification/__tests__/MessageNotification.test.d.ts +1 -0
- package/dist/temp/src/components/overlays/notification/__tests__/MessageNotification.test.js +190 -0
- package/dist/temp/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.d.ts +1 -0
- package/dist/temp/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +160 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +45 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +125 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.d.ts +16 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.js +24 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.d.ts +1 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +503 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/index.d.ts +1 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/index.js +1 -0
- package/dist/temp/src/generated/scoped-css.js +1 -1
- package/dist/types/assets/scripts/notification/FullWidthNotification.d.ts +6 -0
- package/dist/types/assets/scripts/notification/MessageNotification.d.ts +0 -3
- package/dist/types/assets/scripts/notification/const/classNames.d.ts +1 -0
- package/dist/types/assets/scripts/notification/const/index.d.ts +3 -3
- package/dist/types/assets/scripts/notification/const/sizes.d.ts +2 -2
- package/dist/types/assets/scripts/notification/const/types.d.ts +30 -2
- package/dist/types/assets/scripts/notification/messageTemplate.d.ts +15 -0
- package/dist/types/assets/scripts/notification/utils.d.ts +19 -2
- package/dist/types/constant/notification.d.ts +15 -0
- package/dist/types/src/components/forms-and-input/input-base/InputBase.d.ts +1 -1
- package/dist/types/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.d.ts +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/overlays/notification/FullWidthNotification.d.ts +6 -0
- package/dist/types/src/components/overlays/notification/MessageNotification.d.ts +6 -0
- package/dist/types/src/components/overlays/notification/MessageNotificationParts.d.ts +28 -0
- package/dist/types/src/components/overlays/notification/Notification.d.ts +11 -2
- package/dist/types/src/components/overlays/notification/__tests__/MessageNotification.test.d.ts +1 -0
- package/dist/types/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.d.ts +1 -0
- package/dist/types/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +45 -0
- package/dist/types/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.d.ts +16 -0
- package/dist/types/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.d.ts +1 -0
- package/dist/types/src/components/overlays/postcode-search-modal/index.d.ts +1 -0
- package/dist/ui-admin/assets/styles/style.css +280 -35
- package/dist/ui-admin/assets/styles/style.scoped.css +280 -35
- package/package.json +1 -1
package/dist/cjs/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
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 _PasswordInput = require("../PasswordInput");
|
|
8
|
+
// @vitest-environment jsdom
|
|
9
|
+
|
|
10
|
+
// React 18에서 act() 사용 시 필요한 플래그 (미설정 시 console.error 경고 발생)
|
|
11
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
12
|
+
function mountPasswordInput() {
|
|
13
|
+
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
14
|
+
const container = document.createElement('div');
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
const root = (0, _client.createRoot)(container);
|
|
17
|
+
(0, _testUtils.act)(() => {
|
|
18
|
+
root.render(/*#__PURE__*/(0, _react.createElement)(_PasswordInput.PasswordInput, props));
|
|
19
|
+
});
|
|
20
|
+
const unmount = () => {
|
|
21
|
+
(0, _testUtils.act)(() => {
|
|
22
|
+
root.unmount();
|
|
23
|
+
});
|
|
24
|
+
container.remove();
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
container,
|
|
28
|
+
unmount
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
(0, _vitest.describe)('PasswordInput — 자물쇠 아이콘 색상', () => {
|
|
32
|
+
// 자물쇠 색은 CSS 가 결정한다. 인라인 color 를 넘기면 상태 규칙이 무시되므로,
|
|
33
|
+
// "인라인 색이 없고 슬롯 아이콘 공통 클래스를 탄다" 는 것이 색상 반영의 전제다.
|
|
34
|
+
(0, _vitest.it)('자물쇠에 인라인 색을 넘기지 않고 슬롯 아이콘 공통 클래스를 탄다', () => {
|
|
35
|
+
// Given/When: 기본 상태로 렌더하면
|
|
36
|
+
const view = mountPasswordInput();
|
|
37
|
+
// Then: 상태 규칙을 타는 클래스가 붙고 인라인 색은 없다
|
|
38
|
+
const lockIcon = view.container.querySelector('.ncua-input__left-icon');
|
|
39
|
+
(0, _vitest.expect)(lockIcon).not.toBeNull();
|
|
40
|
+
(0, _vitest.expect)(lockIcon?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
41
|
+
(0, _vitest.expect)(lockIcon?.hasAttribute('color')).toBe(false);
|
|
42
|
+
view.unmount();
|
|
43
|
+
});
|
|
44
|
+
// 값이 있는 채로 비활성화되면 has-value 가 살아 있어 자물쇠가 진하게 남던 문제
|
|
45
|
+
(0, _vitest.it)('값이 있는 채로 disabled 여도 자물쇠가 상태 규칙 대상으로 남는다', () => {
|
|
46
|
+
// Given/When: 값이 있는 비활성화 상태로 렌더하면
|
|
47
|
+
const view = mountPasswordInput({
|
|
48
|
+
value: 'secret',
|
|
49
|
+
disabled: true,
|
|
50
|
+
onChange: _vitest.vi.fn()
|
|
51
|
+
});
|
|
52
|
+
// Then: 루트에 is-disabled 와 has-value 가 함께 붙고, 자물쇠는 공통 클래스를 유지한다
|
|
53
|
+
const root = view.container.querySelector('.ncua-input');
|
|
54
|
+
(0, _vitest.expect)(root?.classList.contains('is-disabled')).toBe(true);
|
|
55
|
+
(0, _vitest.expect)(root?.classList.contains('has-value')).toBe(true);
|
|
56
|
+
(0, _vitest.expect)(view.container.querySelector('.ncua-input__left-icon')?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
57
|
+
view.unmount();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -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;
|
|
@@ -23,6 +23,12 @@ const STYLE_HOISTING_PROPS = {
|
|
|
23
23
|
href: STYLE_KEY,
|
|
24
24
|
precedence: 'default'
|
|
25
25
|
};
|
|
26
|
+
// SSR 은 <style> 의 자식 텍스트를 HTML 이스케이프하지만(' -> '), 브라우저는 <style>
|
|
27
|
+
// 안을 raw text 로 읽어 되돌리지 않는다. 그러면 CSS 가 깨지고 하이드레이션도 불일치한다.
|
|
28
|
+
// innerHTML 로 넣으면 서버가 원문 그대로 내보낸다.
|
|
29
|
+
const STYLE_CONTENT = {
|
|
30
|
+
__html: _scopedCss.NCUA_SCOPED_CSS
|
|
31
|
+
};
|
|
26
32
|
/**
|
|
27
33
|
* NCUA 스타일을 이 엘리먼트 하위로 한정하는 경계.
|
|
28
34
|
*
|
|
@@ -59,10 +65,10 @@ const NcuaScope = exports.NcuaScope = /*#__PURE__*/(0, _react.forwardRef)((_ref,
|
|
|
59
65
|
value: true,
|
|
60
66
|
children: [shouldRenderStyle && (SUPPORTS_STYLE_HOISTING ? (0, _jsxRuntime.jsx)("style", {
|
|
61
67
|
...STYLE_HOISTING_PROPS,
|
|
62
|
-
|
|
68
|
+
dangerouslySetInnerHTML: STYLE_CONTENT
|
|
63
69
|
}) : (0, _jsxRuntime.jsx)("style", {
|
|
64
70
|
"data-ncua-scope": STYLE_KEY,
|
|
65
|
-
|
|
71
|
+
dangerouslySetInnerHTML: STYLE_CONTENT
|
|
66
72
|
})), (0, _jsxRuntime.jsx)("div", {
|
|
67
73
|
ref: ref,
|
|
68
74
|
className: (0, _classnames.default)('ncua-scope', className),
|
|
@@ -26,7 +26,9 @@ const Pagination = _ref => {
|
|
|
26
26
|
const [start, setStart] = (0, _react.useState)(1);
|
|
27
27
|
const noPrev = breakPoint === 'mo' ? currentPage === 1 : start === 1;
|
|
28
28
|
const noNext = breakPoint === 'mo' ? currentPage >= totalPage : start + pageCount - 1 >= totalPage;
|
|
29
|
-
|
|
29
|
+
// mo 는 CSS 가 페이지 번호 목록과 first/last 를 숨겨 prev/next 만 남는다.
|
|
30
|
+
// pc 기준(totalPage > pageCount)을 그대로 쓰면 페이지가 적을 때 이동 수단이 하나도 없다.
|
|
31
|
+
const showJumpPageButton = breakPoint === 'mo' ? totalPage > 1 : totalPage > pageCount;
|
|
30
32
|
const handleClickButton = pageNum => {
|
|
31
33
|
if ((0, _lodashEs.isFunction)(onPageChange)) {
|
|
32
34
|
onPageChange(pageNum);
|
|
@@ -15,6 +15,12 @@ const CURRENT_PAGE = 91; // 100페이지 기준 마지막 그룹(91~100)을 보
|
|
|
15
15
|
const GROUP_START_100_PAGES = 91; // currentPage=91 일 때의 그룹 시작
|
|
16
16
|
const GROUP_START_50_PAGES = 41; // totalPage=50 으로 줄었을 때의 마지막 그룹 시작
|
|
17
17
|
const LAST_PAGE_50 = 50;
|
|
18
|
+
/** 페이지 그룹 크기(10) 이하로 나뉘는 건수 — 3페이지 */
|
|
19
|
+
const TOTAL_COUNT_3_PAGES = 30;
|
|
20
|
+
/** TOTAL_COUNT_3_PAGES 기준 총 페이지 수 = 마지막 페이지 번호 */
|
|
21
|
+
const LAST_PAGE_3 = 3;
|
|
22
|
+
/** 페이지가 하나뿐인 건수 */
|
|
23
|
+
const TOTAL_COUNT_1_PAGE = 8;
|
|
18
24
|
let root = null;
|
|
19
25
|
let container = null;
|
|
20
26
|
function render(props) {
|
|
@@ -37,6 +43,8 @@ function render(props) {
|
|
|
37
43
|
container = null;
|
|
38
44
|
});
|
|
39
45
|
const pageNums = el => Array.from(el.querySelectorAll('.ncua-pagination__page-num')).map(b => Number(b.textContent));
|
|
46
|
+
const prevOf = el => el.querySelector('.ncua-pagination__before');
|
|
47
|
+
const nextOf = el => el.querySelector('.ncua-pagination__next');
|
|
40
48
|
(0, _vitest.describe)('#332 Pagination — totalPage 축소 시 start 보정', () => {
|
|
41
49
|
(0, _vitest.it)('totalPage 가 줄면 start 를 마지막 그룹으로 보정하고 범위 밖 페이지 버튼을 표시하지 않는다', () => {
|
|
42
50
|
// 1) 100 페이지에서 91 페이지 보는 중 → 91~100 그룹 표시
|
|
@@ -59,4 +67,70 @@ const pageNums = el => Array.from(el.querySelectorAll('.ncua-pagination__page-nu
|
|
|
59
67
|
(0, _vitest.expect)(nums[0]).toBe(GROUP_START_50_PAGES);
|
|
60
68
|
(0, _vitest.expect)(Math.max(...nums)).toBeLessThanOrEqual(LAST_PAGE_50);
|
|
61
69
|
});
|
|
70
|
+
});
|
|
71
|
+
(0, _vitest.describe)('Pagination — mo 모드 이전/다음 버튼 노출', () => {
|
|
72
|
+
(0, _vitest.it)('페이지가 그룹 크기 이하여도 mo 에서는 이전·다음 버튼이 노출된다', () => {
|
|
73
|
+
// Given/When: 3페이지(그룹 크기 10 이하) 분량을 mo 로 렌더하면
|
|
74
|
+
const el = render({
|
|
75
|
+
totalCount: TOTAL_COUNT_3_PAGES,
|
|
76
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
77
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
78
|
+
currentPage: 1,
|
|
79
|
+
breakPoint: 'mo'
|
|
80
|
+
});
|
|
81
|
+
// Then: mo 는 페이지 번호 목록이 CSS 로 숨겨지므로 이전·다음 버튼이 있어야 이동할 수 있다
|
|
82
|
+
(0, _vitest.expect)(prevOf(el)).not.toBeNull();
|
|
83
|
+
(0, _vitest.expect)(nextOf(el)).not.toBeNull();
|
|
84
|
+
});
|
|
85
|
+
(0, _vitest.it)('첫 페이지에서는 이전 버튼이, 마지막 페이지에서는 다음 버튼이 비활성이다', () => {
|
|
86
|
+
// Given/When: 3페이지 중 첫 페이지를 mo 로 렌더하면
|
|
87
|
+
const first = render({
|
|
88
|
+
totalCount: TOTAL_COUNT_3_PAGES,
|
|
89
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
90
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
91
|
+
currentPage: 1,
|
|
92
|
+
breakPoint: 'mo'
|
|
93
|
+
});
|
|
94
|
+
// Then: 이전만 막히고 다음은 누를 수 있다
|
|
95
|
+
(0, _vitest.expect)(prevOf(first)?.disabled).toBe(true);
|
|
96
|
+
(0, _vitest.expect)(nextOf(first)?.disabled).toBe(false);
|
|
97
|
+
// When: 마지막 페이지로 이동하면
|
|
98
|
+
const last = render({
|
|
99
|
+
totalCount: TOTAL_COUNT_3_PAGES,
|
|
100
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
101
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
102
|
+
currentPage: LAST_PAGE_3,
|
|
103
|
+
breakPoint: 'mo'
|
|
104
|
+
});
|
|
105
|
+
// Then: 반대로 다음이 막힌다
|
|
106
|
+
(0, _vitest.expect)(prevOf(last)?.disabled).toBe(false);
|
|
107
|
+
(0, _vitest.expect)(nextOf(last)?.disabled).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
(0, _vitest.it)('페이지가 하나뿐이면 mo 에서도 이전·다음 버튼을 내보내지 않는다', () => {
|
|
110
|
+
// Given/When: 1페이지 분량을 mo 로 렌더하면
|
|
111
|
+
const el = render({
|
|
112
|
+
totalCount: TOTAL_COUNT_1_PAGE,
|
|
113
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
114
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
115
|
+
currentPage: 1,
|
|
116
|
+
breakPoint: 'mo'
|
|
117
|
+
});
|
|
118
|
+
// Then: 이동할 곳이 없으므로 버튼도 없다
|
|
119
|
+
(0, _vitest.expect)(prevOf(el)).toBeNull();
|
|
120
|
+
(0, _vitest.expect)(nextOf(el)).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
(0, _vitest.it)('pc 는 기존대로 페이지 그룹을 넘길 때만 점프 버튼을 노출한다', () => {
|
|
123
|
+
// Given/When: 3페이지 분량을 pc 로 렌더하면
|
|
124
|
+
const el = render({
|
|
125
|
+
totalCount: TOTAL_COUNT_3_PAGES,
|
|
126
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
127
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
128
|
+
currentPage: 1,
|
|
129
|
+
breakPoint: 'pc'
|
|
130
|
+
});
|
|
131
|
+
// Then: 페이지 번호로 직접 이동하므로 점프 버튼은 나오지 않는다
|
|
132
|
+
(0, _vitest.expect)(prevOf(el)).toBeNull();
|
|
133
|
+
(0, _vitest.expect)(nextOf(el)).toBeNull();
|
|
134
|
+
(0, _vitest.expect)(pageNums(el)).toHaveLength(LAST_PAGE_3);
|
|
135
|
+
});
|
|
62
136
|
});
|
|
@@ -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:
|
|
121
|
+
children: hidePermanentlyLabel
|
|
120
122
|
}), onClose && (0, _jsxRuntime.jsx)("button", {
|
|
121
123
|
type: "button",
|
|
122
124
|
className: "ncua-full-width-notification__close-button",
|
|
@@ -9,8 +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
|
|
13
|
-
var _FeaturedIcon = require("../../image-and-icons/featured-icon/FeaturedIcon");
|
|
12
|
+
var _MessageNotificationParts = require("./MessageNotificationParts");
|
|
14
13
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
14
|
const iconMap = {
|
|
16
15
|
neutral: _uiAdminIcon.Pin02,
|
|
@@ -34,18 +33,11 @@ const MessageNotification = exports.MessageNotification = /*#__PURE__*/(0, _reac
|
|
|
34
33
|
className,
|
|
35
34
|
actions,
|
|
36
35
|
onHidePermanently,
|
|
36
|
+
hidePermanentlyLabel,
|
|
37
37
|
...rest
|
|
38
38
|
} = _ref;
|
|
39
39
|
// message 타입은 neutral, error, warning, success 4가지 색상만 지원
|
|
40
40
|
const validColor = color === 'info' ? 'neutral' : color;
|
|
41
|
-
const iconColor = validColor;
|
|
42
|
-
const featuredIconProps = {
|
|
43
|
-
icon: Icon || iconMap[validColor] || _uiAdminIcon.Pin02,
|
|
44
|
-
size: 'lg',
|
|
45
|
-
color: iconColor,
|
|
46
|
-
theme: 'light-circle'
|
|
47
|
-
};
|
|
48
|
-
const closeIconColor = _color.COLOR[iconColorMap[validColor] || 'gray700'];
|
|
49
41
|
return (0, _jsxRuntime.jsx)("div", {
|
|
50
42
|
ref: ref,
|
|
51
43
|
className: (0, _classnames.default)('ncua-message-notification', `ncua-message-notification--${validColor}`, className),
|
|
@@ -55,50 +47,18 @@ const MessageNotification = exports.MessageNotification = /*#__PURE__*/(0, _reac
|
|
|
55
47
|
className: "ncua-message-notification__container",
|
|
56
48
|
children: (0, _jsxRuntime.jsxs)("div", {
|
|
57
49
|
className: "ncua-message-notification__content",
|
|
58
|
-
children: [(0, _jsxRuntime.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
children: supportingText
|
|
71
|
-
})]
|
|
72
|
-
})]
|
|
73
|
-
}), (0, _jsxRuntime.jsx)("div", {
|
|
74
|
-
className: "ncua-message-notification__actions-container",
|
|
75
|
-
children: actions && (0, _jsxRuntime.jsx)("div", {
|
|
76
|
-
className: "ncua-message-notification__actions",
|
|
77
|
-
children: actions.map(action => (0, _jsxRuntime.jsx)(_button.Button, {
|
|
78
|
-
size: "xs",
|
|
79
|
-
hierarchy: action.hierarchy || 'text',
|
|
80
|
-
label: action.label,
|
|
81
|
-
onClick: action?.onClick
|
|
82
|
-
}, `${action.label}-${action.hierarchy}`))
|
|
83
|
-
})
|
|
84
|
-
}), (0, _jsxRuntime.jsxs)("div", {
|
|
85
|
-
className: "ncua-message-notification__footer-container",
|
|
86
|
-
children: [onHidePermanently && (0, _jsxRuntime.jsx)("button", {
|
|
87
|
-
type: "button",
|
|
88
|
-
className: (0, _classnames.default)('ncua-notification__action-button', 'ncua-notification__action-button--link', 'ncua-message-notification__hide-link'),
|
|
89
|
-
onClick: onHidePermanently,
|
|
90
|
-
children: "\uB2E4\uC2DC \uBCF4\uC9C0 \uC54A\uAE30"
|
|
91
|
-
}), onClose && (0, _jsxRuntime.jsx)("button", {
|
|
92
|
-
type: "button",
|
|
93
|
-
className: "ncua-message-notification__close-button",
|
|
94
|
-
onClick: onClose,
|
|
95
|
-
"aria-label": "\uC54C\uB9BC \uB2EB\uAE30",
|
|
96
|
-
children: (0, _jsxRuntime.jsx)(_uiAdminIcon.XClose, {
|
|
97
|
-
width: 20,
|
|
98
|
-
height: 20,
|
|
99
|
-
color: closeIconColor
|
|
100
|
-
})
|
|
101
|
-
})]
|
|
50
|
+
children: [(0, _jsxRuntime.jsx)(_MessageNotificationParts.MessageNotificationContent, {
|
|
51
|
+
title: title,
|
|
52
|
+
supportingText: supportingText,
|
|
53
|
+
icon: Icon || iconMap[validColor],
|
|
54
|
+
iconColor: validColor,
|
|
55
|
+
showIcon: Boolean(iconMap[validColor])
|
|
56
|
+
}), (0, _jsxRuntime.jsx)(_MessageNotificationParts.MessageNotificationActions, {
|
|
57
|
+
actions: actions,
|
|
58
|
+
onClose: onClose,
|
|
59
|
+
onHidePermanently: onHidePermanently,
|
|
60
|
+
hidePermanentlyLabel: hidePermanentlyLabel,
|
|
61
|
+
closeIconColor: _color.COLOR[iconColorMap[validColor] || 'gray700']
|
|
102
62
|
})]
|
|
103
63
|
})
|
|
104
64
|
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.MessageNotificationContent = exports.MessageNotificationActions = void 0;
|
|
7
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
8
|
+
var _uiAdminIcon = require("@ncds/ui-admin-icon");
|
|
9
|
+
var _notification = require("../../../../constant/notification");
|
|
10
|
+
var _button = require("../../action/button");
|
|
11
|
+
var _FeaturedIcon = require("../../image-and-icons/featured-icon/FeaturedIcon");
|
|
12
|
+
/** 아이콘 + 제목/서포팅 텍스트 영역. */
|
|
13
|
+
const MessageNotificationContent = _ref => {
|
|
14
|
+
let {
|
|
15
|
+
title,
|
|
16
|
+
supportingText,
|
|
17
|
+
icon,
|
|
18
|
+
iconColor,
|
|
19
|
+
showIcon
|
|
20
|
+
} = _ref;
|
|
21
|
+
const featuredIconProps = {
|
|
22
|
+
icon: icon || _uiAdminIcon.Pin02,
|
|
23
|
+
size: 'md',
|
|
24
|
+
color: iconColor,
|
|
25
|
+
theme: 'light-circle'
|
|
26
|
+
};
|
|
27
|
+
return (0, _jsxRuntime.jsxs)("div", {
|
|
28
|
+
className: "ncua-message-notification__content-wrapper",
|
|
29
|
+
children: [showIcon && (0, _jsxRuntime.jsx)(_FeaturedIcon.FeaturedIcon, {
|
|
30
|
+
...featuredIconProps,
|
|
31
|
+
className: "ncua-message-notification__icon"
|
|
32
|
+
}), (0, _jsxRuntime.jsxs)("div", {
|
|
33
|
+
className: "ncua-message-notification__text-container",
|
|
34
|
+
children: [(0, _jsxRuntime.jsx)("span", {
|
|
35
|
+
className: "ncua-message-notification__title",
|
|
36
|
+
children: title
|
|
37
|
+
}), supportingText && (0, _jsxRuntime.jsx)("span", {
|
|
38
|
+
className: "ncua-message-notification__supporting-text",
|
|
39
|
+
children: supportingText
|
|
40
|
+
})]
|
|
41
|
+
})]
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* 액션 버튼 그룹 + 닫기 그룹을 감싸는 우측 영역.
|
|
46
|
+
* 이 컨테이너의 gap(12px)이 두 그룹 사이 간격을 만든다.
|
|
47
|
+
* 표시할 요소가 하나도 없으면 빈 컨테이너를 남기지 않는다.
|
|
48
|
+
*/
|
|
49
|
+
exports.MessageNotificationContent = MessageNotificationContent;
|
|
50
|
+
const MessageNotificationActions = _ref2 => {
|
|
51
|
+
let {
|
|
52
|
+
actions,
|
|
53
|
+
onClose,
|
|
54
|
+
onHidePermanently,
|
|
55
|
+
hidePermanentlyLabel = _notification.HIDE_PERMANENTLY_LABEL.message,
|
|
56
|
+
closeIconColor
|
|
57
|
+
} = _ref2;
|
|
58
|
+
const hasActions = Boolean(actions?.length);
|
|
59
|
+
const hasFooter = Boolean(onHidePermanently || onClose);
|
|
60
|
+
if (!hasActions && !hasFooter) return null;
|
|
61
|
+
return (0, _jsxRuntime.jsxs)("div", {
|
|
62
|
+
className: "ncua-message-notification__actions-container",
|
|
63
|
+
children: [hasActions && (0, _jsxRuntime.jsx)("div", {
|
|
64
|
+
className: "ncua-message-notification__actions",
|
|
65
|
+
children: actions?.map(action => (0, _jsxRuntime.jsx)(_button.Button, {
|
|
66
|
+
size: "xs",
|
|
67
|
+
hierarchy: action.hierarchy || 'text',
|
|
68
|
+
label: action.label,
|
|
69
|
+
onClick: action?.onClick
|
|
70
|
+
}, `${action.label}-${action.hierarchy}`))
|
|
71
|
+
}), hasFooter && (0, _jsxRuntime.jsxs)("div", {
|
|
72
|
+
className: "ncua-message-notification__footer-container",
|
|
73
|
+
children: [onHidePermanently && (0, _jsxRuntime.jsx)("button", {
|
|
74
|
+
type: "button",
|
|
75
|
+
className: "ncua-notification__action-button ncua-message-notification__hide-link",
|
|
76
|
+
onClick: onHidePermanently,
|
|
77
|
+
children: hidePermanentlyLabel
|
|
78
|
+
}), onClose && (0, _jsxRuntime.jsx)("button", {
|
|
79
|
+
type: "button",
|
|
80
|
+
className: "ncua-message-notification__close-button",
|
|
81
|
+
onClick: onClose,
|
|
82
|
+
"aria-label": "\uC54C\uB9BC \uB2EB\uAE30",
|
|
83
|
+
children: (0, _jsxRuntime.jsx)(_uiAdminIcon.XClose, {
|
|
84
|
+
width: 20,
|
|
85
|
+
height: 20,
|
|
86
|
+
color: closeIconColor
|
|
87
|
+
})
|
|
88
|
+
})]
|
|
89
|
+
})]
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
exports.MessageNotificationActions = MessageNotificationActions;
|
|
@@ -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
|
+
});
|