@ncds/ui-admin 1.8.24 → 1.8.25-alpha.1
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/src/components/feedback-and-status/badge/Badge.js +1 -1
- package/dist/cjs/src/components/forms-and-input/combo-box/ComboBox.js +109 -28
- package/dist/cjs/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.js +581 -0
- package/dist/cjs/src/components/navigation/bread-crumb/BreadCrumb.js +14 -6
- package/dist/cjs/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.js +207 -0
- package/dist/cjs/src/generated/scoped-css.js +1 -1
- package/dist/cjs/src/hooks/__tests__/useFloatingPosition.ssr.test.js +29 -0
- package/dist/cjs/src/hooks/__tests__/useFloatingPosition.test.js +322 -0
- package/dist/cjs/src/hooks/dropdown/useDropdown.js +2 -1
- package/dist/cjs/src/hooks/useFloatingPosition.js +14 -53
- package/dist/cjs/src/utils/dropdown/__tests__/dropdownUtils.test.js +316 -0
- package/dist/cjs/src/utils/dropdown/dropdownUtils.js +8 -12
- package/dist/cjs/src/utils/portal-position/__tests__/portalPosition.test.js +227 -0
- package/dist/cjs/src/utils/portal-position/__tests__/portalPosition.tracking.test.js +397 -0
- package/dist/cjs/src/utils/portal-position/index.js +16 -0
- package/dist/cjs/src/utils/portal-position/portalPosition.js +162 -0
- package/dist/esm/src/components/feedback-and-status/badge/Badge.js +1 -1
- package/dist/esm/src/components/forms-and-input/combo-box/ComboBox.js +109 -28
- package/dist/esm/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.js +578 -0
- package/dist/esm/src/components/navigation/bread-crumb/BreadCrumb.js +14 -6
- package/dist/esm/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.js +204 -0
- package/dist/esm/src/generated/scoped-css.js +1 -1
- package/dist/esm/src/hooks/__tests__/useFloatingPosition.ssr.test.js +26 -0
- package/dist/esm/src/hooks/__tests__/useFloatingPosition.test.js +319 -0
- package/dist/esm/src/hooks/dropdown/useDropdown.js +2 -1
- package/dist/esm/src/hooks/useFloatingPosition.js +15 -54
- package/dist/esm/src/utils/dropdown/__tests__/dropdownUtils.test.js +314 -0
- package/dist/esm/src/utils/dropdown/dropdownUtils.js +8 -12
- package/dist/esm/src/utils/portal-position/__tests__/portalPosition.test.js +225 -0
- package/dist/esm/src/utils/portal-position/__tests__/portalPosition.tracking.test.js +394 -0
- package/dist/esm/src/utils/portal-position/index.js +1 -0
- package/dist/esm/src/utils/portal-position/portalPosition.js +150 -0
- package/dist/temp/assets/scripts/notification/const/sizes.d.ts +1 -1
- package/dist/temp/src/components/action/button-group/ButtonGroup.d.ts +1 -1
- package/dist/temp/src/components/feedback-and-status/badge/Badge.d.ts +1 -1
- package/dist/temp/src/components/feedback-and-status/badge/Badge.js +1 -1
- package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.d.ts +6 -0
- package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.js +54 -4
- package/dist/temp/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.d.ts +1 -0
- package/dist/temp/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.js +418 -0
- package/dist/temp/src/components/navigation/bread-crumb/BreadCrumb.d.ts +3 -1
- package/dist/temp/src/components/navigation/bread-crumb/BreadCrumb.js +9 -5
- package/dist/temp/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.d.ts +1 -0
- package/dist/temp/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.js +161 -0
- package/dist/temp/src/generated/scoped-css.js +1 -1
- package/dist/temp/src/hooks/__tests__/useFloatingPosition.ssr.test.d.ts +1 -0
- package/dist/temp/src/hooks/__tests__/useFloatingPosition.ssr.test.js +26 -0
- package/dist/temp/src/hooks/__tests__/useFloatingPosition.test.d.ts +1 -0
- package/dist/temp/src/hooks/__tests__/useFloatingPosition.test.js +205 -0
- package/dist/temp/src/hooks/dropdown/useDropdown.d.ts +3 -1
- package/dist/temp/src/hooks/dropdown/useDropdown.js +2 -2
- package/dist/temp/src/hooks/dropdown/useDropdownPosition.d.ts +3 -3
- package/dist/temp/src/hooks/useFloatingPosition.d.ts +6 -4
- package/dist/temp/src/hooks/useFloatingPosition.js +14 -47
- package/dist/temp/src/types/dropdown/dropdown.d.ts +2 -1
- package/dist/temp/src/utils/dropdown/__tests__/dropdownUtils.test.d.ts +1 -0
- package/dist/temp/src/utils/dropdown/__tests__/dropdownUtils.test.js +174 -0
- package/dist/temp/src/utils/dropdown/dropdownUtils.js +8 -9
- package/dist/temp/src/utils/portal-position/__tests__/portalPosition.test.d.ts +1 -0
- package/dist/temp/src/utils/portal-position/__tests__/portalPosition.test.js +181 -0
- package/dist/temp/src/utils/portal-position/__tests__/portalPosition.tracking.test.d.ts +1 -0
- package/dist/temp/src/utils/portal-position/__tests__/portalPosition.tracking.test.js +301 -0
- package/dist/temp/src/utils/portal-position/index.d.ts +1 -0
- package/dist/temp/src/utils/portal-position/index.js +1 -0
- package/dist/temp/src/utils/portal-position/portalPosition.d.ts +67 -0
- package/dist/temp/src/utils/portal-position/portalPosition.js +88 -0
- package/dist/types/assets/scripts/notification/const/sizes.d.ts +1 -1
- package/dist/types/src/components/action/button-group/ButtonGroup.d.ts +1 -1
- package/dist/types/src/components/feedback-and-status/badge/Badge.d.ts +1 -1
- package/dist/types/src/components/forms-and-input/combo-box/ComboBox.d.ts +6 -0
- package/dist/types/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.d.ts +1 -0
- package/dist/types/src/components/navigation/bread-crumb/BreadCrumb.d.ts +3 -1
- package/dist/types/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.d.ts +1 -0
- package/dist/types/src/hooks/__tests__/useFloatingPosition.ssr.test.d.ts +1 -0
- package/dist/types/src/hooks/__tests__/useFloatingPosition.test.d.ts +1 -0
- package/dist/types/src/hooks/dropdown/useDropdown.d.ts +3 -1
- package/dist/types/src/hooks/dropdown/useDropdownPosition.d.ts +3 -3
- package/dist/types/src/hooks/useFloatingPosition.d.ts +6 -4
- package/dist/types/src/types/dropdown/dropdown.d.ts +2 -1
- package/dist/types/src/utils/dropdown/__tests__/dropdownUtils.test.d.ts +1 -0
- package/dist/types/src/utils/portal-position/__tests__/portalPosition.test.d.ts +1 -0
- package/dist/types/src/utils/portal-position/__tests__/portalPosition.tracking.test.d.ts +1 -0
- package/dist/types/src/utils/portal-position/index.d.ts +1 -0
- package/dist/types/src/utils/portal-position/portalPosition.d.ts +67 -0
- package/dist/ui-admin/assets/styles/style.css +26 -16
- package/package.json +2 -2
|
@@ -0,0 +1,207 @@
|
|
|
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 _BreadCrumb = require("../BreadCrumb");
|
|
8
|
+
// @vitest-environment jsdom
|
|
9
|
+
|
|
10
|
+
const LIST_ITEM = '.ncua-breadcrumb__list-item';
|
|
11
|
+
const HOME_MODIFIER = 'ncua-breadcrumb__list-item--home';
|
|
12
|
+
const ACTIVE_MODIFIER = 'ncua-breadcrumb__list-item--active';
|
|
13
|
+
const ITEM = '.ncua-breadcrumb__item';
|
|
14
|
+
/** DocBreadcrumbs(유일한 사내 호출부)와 같은 형태의 입력 — AC-7 회귀 기준 */
|
|
15
|
+
const DOC_ITEMS = [{
|
|
16
|
+
href: '/',
|
|
17
|
+
label: '홈'
|
|
18
|
+
}, {
|
|
19
|
+
href: '/docs',
|
|
20
|
+
label: '문서'
|
|
21
|
+
}, {
|
|
22
|
+
label: '현재 페이지'
|
|
23
|
+
}];
|
|
24
|
+
let root = null;
|
|
25
|
+
let container = null;
|
|
26
|
+
function render(props) {
|
|
27
|
+
if (!container) {
|
|
28
|
+
container = document.createElement('div');
|
|
29
|
+
document.body.appendChild(container);
|
|
30
|
+
root = (0, _client.createRoot)(container);
|
|
31
|
+
}
|
|
32
|
+
(0, _testUtils.act)(() => {
|
|
33
|
+
root?.render(/*#__PURE__*/(0, _react.createElement)(_BreadCrumb.BreadCrumb, props));
|
|
34
|
+
});
|
|
35
|
+
return container;
|
|
36
|
+
}
|
|
37
|
+
(0, _vitest.afterEach)(() => {
|
|
38
|
+
if (root && container) {
|
|
39
|
+
(0, _testUtils.act)(() => root?.unmount());
|
|
40
|
+
container.remove();
|
|
41
|
+
}
|
|
42
|
+
root = null;
|
|
43
|
+
container = null;
|
|
44
|
+
});
|
|
45
|
+
const listItems = el => Array.from(el.querySelectorAll(LIST_ITEM));
|
|
46
|
+
/** <li> 안에서 실제 포커스를 받는 요소(<a> 또는 <span>) */
|
|
47
|
+
const itemOf = li => li.querySelector(ITEM);
|
|
48
|
+
(0, _vitest.describe)('BreadCrumb — showHomeIcon 아이콘/라벨 분기', () => {
|
|
49
|
+
(0, _vitest.it)('AC-1: showHomeIcon 미지정이면 첫 항목을 홈 아이콘으로 렌더한다', () => {
|
|
50
|
+
// Given/When: showHomeIcon 을 넘기지 않고 렌더하면
|
|
51
|
+
const el = render({
|
|
52
|
+
items: DOC_ITEMS
|
|
53
|
+
});
|
|
54
|
+
// Then: 첫 항목은 --home 모디파이어를 달고 svg 를 품으며, label 텍스트는 노출되지 않는다
|
|
55
|
+
const [first] = listItems(el);
|
|
56
|
+
(0, _vitest.expect)(first.classList.contains(HOME_MODIFIER)).toBe(true);
|
|
57
|
+
(0, _vitest.expect)(first.querySelector('svg')).not.toBeNull();
|
|
58
|
+
(0, _vitest.expect)(first.textContent).not.toContain(DOC_ITEMS[0].label);
|
|
59
|
+
});
|
|
60
|
+
(0, _vitest.it)('AC-2: showHomeIcon={false} 이면 첫 항목을 label 텍스트로 렌더한다', () => {
|
|
61
|
+
// Given/When: 라벨 모드로 렌더하면
|
|
62
|
+
const el = render({
|
|
63
|
+
items: DOC_ITEMS,
|
|
64
|
+
showHomeIcon: false
|
|
65
|
+
});
|
|
66
|
+
// Then: 아이콘 대신 label 텍스트가 나오고 --home 은 붙지 않는다
|
|
67
|
+
const [first] = listItems(el);
|
|
68
|
+
(0, _vitest.expect)(first.classList.contains(HOME_MODIFIER)).toBe(false);
|
|
69
|
+
(0, _vitest.expect)(first.querySelector('svg')).toBeNull();
|
|
70
|
+
(0, _vitest.expect)(first.textContent).toBe(DOC_ITEMS[0].label);
|
|
71
|
+
});
|
|
72
|
+
(0, _vitest.it)('AC-3: 홈 아이콘 항목의 링크에 label 값이 aria-label 로 부여된다', () => {
|
|
73
|
+
// Given/When: 아이콘 모드로 렌더하면
|
|
74
|
+
const el = render({
|
|
75
|
+
items: DOC_ITEMS
|
|
76
|
+
});
|
|
77
|
+
// Then: 접근 가능한 이름은 <li> 가 아니라 포커스를 받는 <a> 에 붙는다
|
|
78
|
+
const [first] = listItems(el);
|
|
79
|
+
const anchor = itemOf(first);
|
|
80
|
+
(0, _vitest.expect)(anchor?.tagName).toBe('A');
|
|
81
|
+
(0, _vitest.expect)(anchor?.getAttribute('aria-label')).toBe(DOC_ITEMS[0].label);
|
|
82
|
+
});
|
|
83
|
+
(0, _vitest.it)('AC-4(라벨 모드): 어떤 항목에도 --home 과 aria-label 이 붙지 않는다', () => {
|
|
84
|
+
// Given/When: 라벨 모드로 렌더하면
|
|
85
|
+
const el = render({
|
|
86
|
+
items: DOC_ITEMS,
|
|
87
|
+
showHomeIcon: false
|
|
88
|
+
});
|
|
89
|
+
// Then: --home 은 전 항목에서 사라지고, 첫 항목의 aria-label 속성 자체가 출력되지 않는다
|
|
90
|
+
const items = listItems(el);
|
|
91
|
+
(0, _vitest.expect)(items.every(li => !li.classList.contains(HOME_MODIFIER))).toBe(true);
|
|
92
|
+
(0, _vitest.expect)(itemOf(items[0])?.hasAttribute('aria-label')).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
(0, _vitest.it)('AC-4(아이콘 모드): --home 은 첫 항목에만 붙는다', () => {
|
|
95
|
+
// Given/When: 아이콘 모드로 렌더하면
|
|
96
|
+
const el = render({
|
|
97
|
+
items: DOC_ITEMS
|
|
98
|
+
});
|
|
99
|
+
// Then: 홈 아이콘인 첫 항목에만 붙고 나머지에는 붙지 않는다
|
|
100
|
+
const items = listItems(el);
|
|
101
|
+
(0, _vitest.expect)(items.filter(li => li.classList.contains(HOME_MODIFIER))).toHaveLength(1);
|
|
102
|
+
(0, _vitest.expect)(items[0].classList.contains(HOME_MODIFIER)).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
(0, _vitest.it)('AC-5: 마지막 항목은 showHomeIcon 과 무관하게 --active 를 달고 링크가 아니다', () => {
|
|
105
|
+
// Given/When: 아이콘 모드로 렌더하면
|
|
106
|
+
const iconMode = listItems(render({
|
|
107
|
+
items: DOC_ITEMS
|
|
108
|
+
}));
|
|
109
|
+
const iconLast = iconMode[iconMode.length - 1];
|
|
110
|
+
// Then: --active 가 붙고 <span> 이라 클릭 이동이 없다
|
|
111
|
+
(0, _vitest.expect)(iconLast.classList.contains(ACTIVE_MODIFIER)).toBe(true);
|
|
112
|
+
(0, _vitest.expect)(itemOf(iconLast)?.tagName).toBe('SPAN');
|
|
113
|
+
// When: 라벨 모드로 다시 렌더해도
|
|
114
|
+
const labelMode = listItems(render({
|
|
115
|
+
items: DOC_ITEMS,
|
|
116
|
+
showHomeIcon: false
|
|
117
|
+
}));
|
|
118
|
+
const labelLast = labelMode[labelMode.length - 1];
|
|
119
|
+
// Then: 판정은 showHomeIcon 과 독립이다
|
|
120
|
+
(0, _vitest.expect)(labelLast.classList.contains(ACTIVE_MODIFIER)).toBe(true);
|
|
121
|
+
(0, _vitest.expect)(itemOf(labelLast)?.tagName).toBe('SPAN');
|
|
122
|
+
});
|
|
123
|
+
(0, _vitest.it)('AC-6: 단일 항목이면 첫 항목이 곧 마지막 항목으로 처리된다', () => {
|
|
124
|
+
// Given/When: 항목이 하나뿐인 배열을 아이콘 모드로 렌더하면
|
|
125
|
+
const single = [{
|
|
126
|
+
href: '/',
|
|
127
|
+
label: '홈'
|
|
128
|
+
}];
|
|
129
|
+
const [iconOnly] = listItems(render({
|
|
130
|
+
items: single
|
|
131
|
+
}));
|
|
132
|
+
// Then: --home 과 --active 가 동시에 붙고 링크가 아니다
|
|
133
|
+
(0, _vitest.expect)(iconOnly.classList.contains(HOME_MODIFIER)).toBe(true);
|
|
134
|
+
(0, _vitest.expect)(iconOnly.classList.contains(ACTIVE_MODIFIER)).toBe(true);
|
|
135
|
+
(0, _vitest.expect)(itemOf(iconOnly)?.tagName).toBe('SPAN');
|
|
136
|
+
(0, _vitest.expect)(iconOnly.querySelector('svg')).not.toBeNull();
|
|
137
|
+
// When: 같은 배열을 라벨 모드로 렌더하면
|
|
138
|
+
const [labelOnly] = listItems(render({
|
|
139
|
+
items: single,
|
|
140
|
+
showHomeIcon: false
|
|
141
|
+
}));
|
|
142
|
+
// Then: --active 만 붙고 label 텍스트가 나온다
|
|
143
|
+
(0, _vitest.expect)(labelOnly.classList.contains(HOME_MODIFIER)).toBe(false);
|
|
144
|
+
(0, _vitest.expect)(labelOnly.classList.contains(ACTIVE_MODIFIER)).toBe(true);
|
|
145
|
+
(0, _vitest.expect)(labelOnly.textContent).toBe(single[0].label);
|
|
146
|
+
});
|
|
147
|
+
(0, _vitest.it)('AC-7: 기존 호출부 입력의 렌더 결과는 --home·aria-label 추가분 외에 변하지 않는다', () => {
|
|
148
|
+
// Given/When: DocBreadcrumbs 와 같은 입력을 showHomeIcon 미지정으로 렌더하면
|
|
149
|
+
const el = render({
|
|
150
|
+
items: DOC_ITEMS
|
|
151
|
+
});
|
|
152
|
+
const items = listItems(el);
|
|
153
|
+
// Then: 요소 종류·텍스트·href 는 변경 전과 같고, 첫 항목 svg 도 그대로다
|
|
154
|
+
(0, _vitest.expect)(items).toHaveLength(DOC_ITEMS.length);
|
|
155
|
+
(0, _vitest.expect)(items.map(li => itemOf(li)?.tagName)).toEqual(['A', 'A', 'SPAN']);
|
|
156
|
+
(0, _vitest.expect)(items.map(li => itemOf(li)?.getAttribute('href'))).toEqual(['/', '/docs', null]);
|
|
157
|
+
(0, _vitest.expect)(items.map(li => li.textContent)).toEqual(['', DOC_ITEMS[1].label, DOC_ITEMS[2].label]);
|
|
158
|
+
(0, _vitest.expect)(items[0].querySelector('svg')).not.toBeNull();
|
|
159
|
+
// Then: <li> 클래스는 기존 베이스/--active 를 그대로 유지하고, 늘어난 것은 --home 뿐이다
|
|
160
|
+
(0, _vitest.expect)(items.map(li => Array.from(li.classList).sort())).toEqual([['ncua-breadcrumb__list-item', HOME_MODIFIER].sort(), ['ncua-breadcrumb__list-item'], ['ncua-breadcrumb__list-item', ACTIVE_MODIFIER].sort()]);
|
|
161
|
+
// Then: <a>/<span> 에 늘어난 속성은 첫 항목의 aria-label 뿐이다
|
|
162
|
+
(0, _vitest.expect)(itemOf(items[1])?.hasAttribute('aria-label')).toBe(false);
|
|
163
|
+
(0, _vitest.expect)(itemOf(items[2])?.hasAttribute('aria-label')).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
(0, _vitest.it)('비링크 홈 항목에는 role="img" 를 부여해 aria-label 이 무시되지 않게 한다', () => {
|
|
166
|
+
// Given/When: 첫 항목에 href 가 없어 <span> 으로 렌더되는 입력(meta usageExamples.default 형태)을 아이콘 모드로 렌더하면
|
|
167
|
+
const el = render({
|
|
168
|
+
items: [{
|
|
169
|
+
label: '대시보드'
|
|
170
|
+
}, {
|
|
171
|
+
href: '/products',
|
|
172
|
+
label: '상품'
|
|
173
|
+
}, {
|
|
174
|
+
label: '상품 상세'
|
|
175
|
+
}]
|
|
176
|
+
});
|
|
177
|
+
// Then: generic role 의 <span> 은 aria-label 을 노출하지 못하므로 role="img" 로 이름을 살린다
|
|
178
|
+
const [first] = listItems(el);
|
|
179
|
+
const home = itemOf(first);
|
|
180
|
+
(0, _vitest.expect)(home?.tagName).toBe('SPAN');
|
|
181
|
+
(0, _vitest.expect)(home?.getAttribute('role')).toBe('img');
|
|
182
|
+
(0, _vitest.expect)(home?.getAttribute('aria-label')).toBe('대시보드');
|
|
183
|
+
});
|
|
184
|
+
(0, _vitest.it)('링크인 홈 항목에는 role 을 부여하지 않는다', () => {
|
|
185
|
+
// Given/When: 첫 항목에 href 가 있어 <a> 로 렌더되는 입력을 아이콘 모드로 렌더하면
|
|
186
|
+
const el = render({
|
|
187
|
+
items: DOC_ITEMS
|
|
188
|
+
});
|
|
189
|
+
// Then: 링크는 이미 접근 가능한 이름을 지원하므로 role 을 덧씌우지 않는다 (ADR-3)
|
|
190
|
+
const items = listItems(el);
|
|
191
|
+
const home = itemOf(items[0]);
|
|
192
|
+
(0, _vitest.expect)(home?.tagName).toBe('A');
|
|
193
|
+
(0, _vitest.expect)(home?.hasAttribute('role')).toBe(false);
|
|
194
|
+
// Then: 홈이 아닌 항목에도 role 은 출력되지 않는다
|
|
195
|
+
(0, _vitest.expect)(items.slice(1).every(li => !itemOf(li)?.hasAttribute('role'))).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
(0, _vitest.it)('경계: items 가 빈 배열이면 아무것도 렌더하지 않는다', () => {
|
|
198
|
+
// Given/When: 빈 배열을 양쪽 모드로 렌더하면
|
|
199
|
+
const el = render({
|
|
200
|
+
items: [],
|
|
201
|
+
showHomeIcon: false
|
|
202
|
+
});
|
|
203
|
+
// Then: <nav> 자체가 만들어지지 않는다 (기존 동작 유지)
|
|
204
|
+
(0, _vitest.expect)(el.querySelector('.ncua-breadcrumb')).toBeNull();
|
|
205
|
+
(0, _vitest.expect)(el.innerHTML).toBe('');
|
|
206
|
+
});
|
|
207
|
+
});
|