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

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 (25) hide show
  1. package/dist/cjs/src/components/navigation/pagination/Pagination.js +3 -1
  2. package/dist/cjs/src/components/navigation/pagination/__tests__/Pagination.test.js +74 -0
  3. package/dist/cjs/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +66 -90
  4. package/dist/cjs/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.js +70 -0
  5. package/dist/cjs/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +96 -39
  6. package/dist/cjs/src/generated/scoped-css.js +1 -1
  7. package/dist/esm/src/components/navigation/pagination/Pagination.js +3 -1
  8. package/dist/esm/src/components/navigation/pagination/__tests__/Pagination.test.js +74 -0
  9. package/dist/esm/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +66 -90
  10. package/dist/esm/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.js +63 -0
  11. package/dist/esm/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +96 -39
  12. package/dist/esm/src/generated/scoped-css.js +1 -1
  13. package/dist/temp/src/components/navigation/pagination/Pagination.js +3 -1
  14. package/dist/temp/src/components/navigation/pagination/__tests__/Pagination.test.js +74 -0
  15. package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +3 -7
  16. package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +34 -27
  17. package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.d.ts +16 -0
  18. package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.js +24 -0
  19. package/dist/temp/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +82 -35
  20. package/dist/temp/src/generated/scoped-css.js +1 -1
  21. package/dist/types/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +3 -7
  22. package/dist/types/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.d.ts +16 -0
  23. package/dist/ui-admin/assets/styles/style.css +43 -5
  24. package/dist/ui-admin/assets/styles/style.scoped.css +43 -5
  25. package/package.json +1 -1
@@ -10,11 +10,11 @@ var _PostcodeSearchModal = require("../PostcodeSearchModal");
10
10
  // React 18에서 act() 사용 시 필요한 플래그 (미설정 시 console.error 경고 발생)
11
11
  globalThis.IS_REACT_ACT_ENVIRONMENT = true;
12
12
  const PAGE_SIZE = 15;
13
- /** Tip 압축 임계값(30) 이하 결과 건수 — Tip 미노출 케이스 */
13
+ /** 페이지에 들어가는 결과 건수 — Tip 카드 미노출 케이스 */
14
14
  const SMALL_RESULT_COUNT = 5;
15
15
  /** SMALL_RESULT_COUNT 기준 총 페이지 수 */
16
16
  const SMALL_RESULT_TOTAL_PAGE = 1;
17
- /** Tip 압축 임계값(30) 초과 결과 건수 — Tip 압축 + totalPage(3) <= 페이지 그룹 크기(10) */
17
+ /** 여러 페이지로 나뉘는 결과 건수 — totalPage(3) <= 페이지 그룹 크기(10) */
18
18
  const LARGE_RESULT_COUNT = 45;
19
19
  /** LARGE_RESULT_COUNT 기준 총 페이지 수 */
20
20
  const LARGE_RESULT_TOTAL_PAGE = 3;
@@ -22,6 +22,10 @@ const LARGE_RESULT_TOTAL_PAGE = 3;
22
22
  const HUGE_RESULT_COUNT = 200;
23
23
  /** 클릭 대상 페이지 번호 */
24
24
  const TARGET_PAGE = 3;
25
+ /** 본문을 아래로 내린 상태를 흉내 낼 scrollTop 값 */
26
+ const SCROLLED_OFFSET = 120;
27
+ /** 맨 위로 되돌린 scrollTop 값 */
28
+ const SCROLL_TOP = 0;
25
29
  /** 결과 더미 생성 기준값 */
26
30
  const ZIP_CODE_BASE = 13529;
27
31
  const ROAD_NUMBER_BASE = 242;
@@ -96,7 +100,23 @@ function baseProps() {
96
100
  };
97
101
  }
98
102
  const tipOf = el => el.querySelector('.ncua-postcode-search-modal__tip');
99
- const isCompactTip = el => Boolean(el.querySelector('.ncua-postcode-search-modal__tip-compact-text'));
103
+ const searchAreaOf = el => el.querySelector('.ncua-postcode-search-modal__search-area');
104
+ const isFixedSearchArea = el => Boolean(searchAreaOf(el)?.classList.contains('is-fixed'));
105
+ const scrollerOf = el => el.querySelector('.ncua-modal__content');
106
+ /** jsdom 은 레이아웃이 없어 scrollTop 이 항상 0 이므로, 값을 덮어쓰고 scroll 이벤트를 쏜다. */
107
+ function scrollContent(el, scrollTop) {
108
+ const scroller = scrollerOf(el);
109
+ if (!scroller) throw new Error('모달 본문 스크롤 컨테이너를 찾을 수 없습니다');
110
+ Object.defineProperty(scroller, 'scrollTop', {
111
+ value: scrollTop,
112
+ configurable: true
113
+ });
114
+ (0, _testUtils.act)(() => {
115
+ scroller.dispatchEvent(new Event('scroll'));
116
+ });
117
+ }
118
+ const resultTitleOf = el => el.querySelector('.ncua-postcode-search-modal__result-title');
119
+ const resultDescOf = el => el.querySelector('.ncua-postcode-search-modal__result-desc');
100
120
  const countLabelOf = el => el.querySelector('.ncua-postcode-search-modal__count');
101
121
  const itemsOf = el => el.querySelectorAll('.ncua-postcode-search-modal__item');
102
122
  const inputRootOf = el => el.querySelector('.ncua-input');
@@ -116,13 +136,13 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
116
136
  _vitest.vi.unstubAllGlobals();
117
137
  });
118
138
  (0, _vitest.describe)('PostcodeSearchModal — Case 1: 검색 전 초기 상태', () => {
119
- (0, _vitest.it)('상세 Tip 노출되고 카운트·리스트·페이지네이션·EmptyState 는 렌더되지 않는다', () => {
139
+ (0, _vitest.it)('Tip 카드만 노출되고 카운트·리스트·페이지네이션·EmptyState 는 렌더되지 않는다', () => {
120
140
  // Given: 검색어·결과가 비어 있고 에러도 없는 상태에서
121
141
  // When: 모달을 열면
122
142
  const el = render(baseProps());
123
- // Then: 상세 Tip 노출되고 결과 영역은 전부 미노출이다
143
+ // Then: Tip 카드만 노출되고 결과 영역은 전부 미노출이다
124
144
  (0, _vitest.expect)(tipOf(el)).not.toBeNull();
125
- (0, _vitest.expect)(isCompactTip(el)).toBe(false);
145
+ (0, _vitest.expect)(resultTitleOf(el)).toBeNull();
126
146
  (0, _vitest.expect)(countLabelOf(el)).toBeNull();
127
147
  (0, _vitest.expect)(el.querySelector('.ncua-postcode-search-modal__list')).toBeNull();
128
148
  (0, _vitest.expect)(paginationOf(el)).toBeNull();
@@ -157,15 +177,15 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
157
177
  (0, _vitest.expect)(inputRootOf(el)?.classList.contains('destructive')).toBe(true);
158
178
  (0, _vitest.expect)(hintOf(el)?.textContent).toBe(hint);
159
179
  });
160
- (0, _vitest.it)('에러 상태에서는 상세 Tip 노출되고 리스트·EmptyState 는 미노출이다', () => {
180
+ (0, _vitest.it)('에러 상태에서는 Tip 카드가 노출되고 리스트·EmptyState 는 미노출이다', () => {
161
181
  // Given/When: 에러코드를 전달해 렌더하면
162
182
  const el = render(baseProps({
163
183
  searchQuery: '서',
164
184
  errorCode: 'E0008'
165
185
  }));
166
- // Then: 상세 Tip 남는다
186
+ // Then: Tip 카드만 남는다
167
187
  (0, _vitest.expect)(tipOf(el)).not.toBeNull();
168
- (0, _vitest.expect)(isCompactTip(el)).toBe(false);
188
+ (0, _vitest.expect)(resultTitleOf(el)).toBeNull();
169
189
  (0, _vitest.expect)(el.querySelector('.ncua-postcode-search-modal__list')).toBeNull();
170
190
  (0, _vitest.expect)(emptyStateOf(el)).toBeNull();
171
191
  (0, _vitest.expect)(paginationOf(el)).toBeNull();
@@ -185,11 +205,11 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
185
205
  results: [],
186
206
  totalCount: 0
187
207
  }));
188
- // Then: "검색 결과가 없습니다." 는 뜨지 않고 상세 Tip 유지된다
208
+ // Then: "검색 결과가 없습니다." 는 뜨지 않고 Tip 카드만 유지된다
189
209
  (0, _vitest.expect)(emptyStateOf(el)).toBeNull();
190
210
  (0, _vitest.expect)(tipOf(el)).not.toBeNull();
191
211
  });
192
- (0, _vitest.it)('EmptyState 가 "검색 결과가 없습니다." 로 렌더되고 상세 Tip 유지된다', () => {
212
+ (0, _vitest.it)('EmptyState 가 "검색 결과가 없습니다." 로 렌더되고 Tip 카드가 유지된다', () => {
193
213
  // Given: 검색어는 있으나 결과가 0건인 상태에서
194
214
  const el = render(baseProps({
195
215
  searchQuery: '존재하지않는주소',
@@ -203,14 +223,14 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
203
223
  // Then: EmptyState 가 노출되고 오류 스타일은 아니며 페이지네이션은 없다
204
224
  (0, _vitest.expect)(emptyStateOf(el)?.textContent).toContain('검색 결과가 없습니다.');
205
225
  (0, _vitest.expect)(tipOf(el)).not.toBeNull();
206
- (0, _vitest.expect)(isCompactTip(el)).toBe(false);
226
+ (0, _vitest.expect)(resultTitleOf(el)).toBeNull();
207
227
  (0, _vitest.expect)(inputRootOf(el)?.classList.contains('destructive')).toBe(false);
208
228
  (0, _vitest.expect)(paginationOf(el)).toBeNull();
209
229
  (0, _vitest.expect)(countLabelOf(el)).toBeNull();
210
230
  });
211
231
  });
212
- (0, _vitest.describe)('PostcodeSearchModal — Case 6: 결과 30건 이하', () => {
213
- (0, _vitest.it)('카운트 "검색 5개" 와 5행 리스트가 렌더되고 Tip 미노출된다', () => {
232
+ (0, _vitest.describe)('PostcodeSearchModal — Case 6: 결과 있음', () => {
233
+ (0, _vitest.it)('카운트 "검색 5개" 와 5행 리스트가 렌더되고 Tip 카드는 미노출된다', () => {
214
234
  // Given/When: 5건 결과를 전달하면
215
235
  const results = makeResults(SMALL_RESULT_COUNT);
216
236
  const el = render(baseProps({
@@ -218,7 +238,7 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
218
238
  results,
219
239
  totalCount: SMALL_RESULT_COUNT
220
240
  }));
221
- // Then: 카운트·리스트가 노출되고 Tip 사라진다
241
+ // Then: 카운트·리스트가 노출되고 Tip 카드는 사라진다
222
242
  (0, _vitest.expect)(countLabelOf(el)?.textContent).toBe(`검색 ${SMALL_RESULT_COUNT}개`);
223
243
  (0, _vitest.expect)(itemsOf(el)).toHaveLength(SMALL_RESULT_COUNT);
224
244
  (0, _vitest.expect)(tipOf(el)).toBeNull();
@@ -255,19 +275,34 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
255
275
  (0, _vitest.expect)(el.querySelectorAll('.ncua-pagination__page-num')).toHaveLength(SMALL_RESULT_TOTAL_PAGE);
256
276
  });
257
277
  });
258
- (0, _vitest.describe)('PostcodeSearchModal — Case 7: 결과 30건 초과', () => {
259
- (0, _vitest.it)('카운트 "검색 45개" 와 15행 리스트, 압축 Tip 렌더된다', () => {
278
+ (0, _vitest.describe)('PostcodeSearchModal — Case 7: 결과 여러 페이지', () => {
279
+ (0, _vitest.it)('카운트 "검색 45개" 와 15행 리스트가 렌더되고 Tip 카드 대신 결과 안내 문구가 남는다', () => {
260
280
  // Given/When: 45건 중 첫 페이지 15건을 전달하면
261
281
  const el = render(baseProps({
262
282
  searchQuery: '판교로',
263
283
  results: makeResults(PAGE_SIZE),
264
284
  totalCount: LARGE_RESULT_COUNT
265
285
  }));
266
- // Then: 압축 Tip 노출된다
286
+ // Then: Tip 카드는 사라지고 검색 영역의 안내 문구(제목·조합)가 남는다
267
287
  (0, _vitest.expect)(countLabelOf(el)?.textContent).toBe(`검색 ${LARGE_RESULT_COUNT}개`);
268
288
  (0, _vitest.expect)(itemsOf(el)).toHaveLength(PAGE_SIZE);
269
- (0, _vitest.expect)(tipOf(el)).not.toBeNull();
270
- (0, _vitest.expect)(isCompactTip(el)).toBe(true);
289
+ (0, _vitest.expect)(tipOf(el)).toBeNull();
290
+ (0, _vitest.expect)(resultTitleOf(el)?.textContent).toBe('아래 조합으로 검색하면 원하는 주소를 더 쉽게 찾을 수 있어요.');
291
+ (0, _vitest.expect)(resultDescOf(el)?.textContent).toBe('도로명+건물번호, 지역명+지번, 지역명+건물명(아파트명)');
292
+ });
293
+ (0, _vitest.it)('검색 인풋과 안내 문구가 같은 고정 영역 안에 묶이고 결과 목록은 그 밖에 있다', () => {
294
+ // Given/When: 결과가 여러 페이지인 상태로 렌더하면
295
+ const el = render(baseProps({
296
+ searchQuery: '판교로',
297
+ results: makeResults(PAGE_SIZE),
298
+ totalCount: LARGE_RESULT_COUNT
299
+ }));
300
+ // Then: 상단 고정 영역이 인풋 행과 안내 문구를 함께 감싸고, 스크롤되는 목록은 밖에 남는다
301
+ const searchArea = searchAreaOf(el);
302
+ (0, _vitest.expect)(searchArea?.querySelector('.ncua-postcode-search-modal__search')).not.toBeNull();
303
+ (0, _vitest.expect)(searchArea?.querySelector('.ncua-postcode-search-modal__result-title')).not.toBeNull();
304
+ (0, _vitest.expect)(searchArea?.querySelector('.ncua-postcode-search-modal__result-desc')).not.toBeNull();
305
+ (0, _vitest.expect)(searchArea?.querySelector('.ncua-postcode-search-modal__list')).toBeNull();
271
306
  });
272
307
  (0, _vitest.it)('totalPage(3) 가 PAGER_WINDOW(10) 이하이므로 점프 버튼은 노출되지 않는다', () => {
273
308
  // Given/When: totalCount=45, PAGE_SIZE=15 → totalPage=3
@@ -294,7 +329,8 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
294
329
  (0, _vitest.expect)(el.querySelector('.ncua-pagination__before')).not.toBeNull();
295
330
  (0, _vitest.expect)(el.querySelector('.ncua-pagination__next')).not.toBeNull();
296
331
  (0, _vitest.expect)(el.querySelector('.ncua-pagination__last')).not.toBeNull();
297
- (0, _vitest.expect)(isCompactTip(el)).toBe(true);
332
+ (0, _vitest.expect)(tipOf(el)).toBeNull();
333
+ (0, _vitest.expect)(resultTitleOf(el)).not.toBeNull();
298
334
  });
299
335
  (0, _vitest.it)('3페이지 버튼을 클릭하면 onPageChange(3) 이 호출된다', () => {
300
336
  // Given: 대량 결과가 렌더된 상태에서
@@ -524,8 +560,8 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
524
560
  });
525
561
  });
526
562
  (0, _vitest.describe)('PostcodeSearchModal.Tip — 서브컴포넌트', () => {
527
- (0, _vitest.it)('상세 모드는 조합 3종과 예시 주소를 모두 보여준다', () => {
528
- // Given/When: 상세 Tip 노출되는 초기 상태로 렌더하면
563
+ (0, _vitest.it)('조합 3종과 예시 주소를 모두 보여준다', () => {
564
+ // Given/When: Tip 카드가 노출되는 초기 상태로 렌더하면
529
565
  const el = render(baseProps());
530
566
  // Then: 안내 문구와 3개 조합, 예시 주소가 모두 포함된다
531
567
  const tipText = tipOf(el)?.textContent ?? '';
@@ -537,23 +573,8 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
537
573
  (0, _vitest.expect)(tipText).toContain('지역명(동·리) + 건물명(아파트명)');
538
574
  (0, _vitest.expect)(tipText).toContain('예) 구로동 대륭포스트타워8차');
539
575
  });
540
- (0, _vitest.it)('압축 모드는 조합 3종만 보여주고 예시는 생략한다', () => {
541
- // Given/When: 30건 초과 결과로 압축 Tip 을 렌더하면
542
- const el = render(baseProps({
543
- searchQuery: '판교로',
544
- results: makeResults(PAGE_SIZE),
545
- totalCount: LARGE_RESULT_COUNT
546
- }));
547
- // Then: 조합만 한 줄로 남고 예시 주소는 없다
548
- const tipText = tipOf(el)?.textContent ?? '';
549
- (0, _vitest.expect)(tipText).toContain('아래 조합으로 검색하면 원하는 주소를 더 쉽게 찾을 수 있어요.');
550
- (0, _vitest.expect)(tipText).toContain("'도로명+건물번호', '지역명+지번', '지역명+건물명(아파트명)'");
551
- (0, _vitest.expect)(tipText).not.toContain('디지털로26길 43');
552
- (0, _vitest.expect)(tipText).not.toContain('구로동 235-3');
553
- (0, _vitest.expect)(tipText).not.toContain('구로동 대륭포스트타워8차');
554
- });
555
576
  (0, _vitest.it)('Tip 아이콘은 하드코딩 SVG 가 아니라 아이콘 슬롯 클래스로 렌더된다', () => {
556
- // Given/When: 상세 Tip 렌더하면
577
+ // Given/When: Tip 카드를 렌더하면
557
578
  const el = render(baseProps());
558
579
  // Then: 지정한 아이콘 클래스를 가진 svg 가 존재한다
559
580
  (0, _vitest.expect)(tipOf(el)?.querySelector('svg.ncua-postcode-search-modal__tip-icon')).not.toBeNull();
@@ -563,4 +584,40 @@ const paginationOf = el => el.querySelector('.ncua-pagination');
563
584
  (0, _vitest.expect)(typeof _PostcodeSearchModal.PostcodeSearchModal.Tip).toBe('function');
564
585
  (0, _vitest.expect)(_PostcodeSearchModal.PostcodeSearchModal.Tip.displayName).toBe('PostcodeSearchModal.Tip');
565
586
  });
587
+ });
588
+ (0, _vitest.describe)('PostcodeSearchModal — 검색 영역 고정 표시', () => {
589
+ (0, _vitest.it)('결과가 있어도 스크롤하기 전에는 is-fixed 가 붙지 않는다', () => {
590
+ // Given/When: 결과를 스크롤하지 않은 상태로 렌더하면
591
+ const el = render(baseProps({
592
+ searchQuery: '판교로',
593
+ results: makeResults(PAGE_SIZE),
594
+ totalCount: LARGE_RESULT_COUNT
595
+ }));
596
+ // Then: 검색 영역이 아직 떠 있지 않으므로 경계선 클래스도 없다
597
+ (0, _vitest.expect)(isFixedSearchArea(el)).toBe(false);
598
+ });
599
+ (0, _vitest.it)('결과를 스크롤하면 is-fixed 가 붙고, 맨 위로 돌아오면 다시 사라진다', () => {
600
+ // Given: 결과가 렌더된 상태에서
601
+ const el = render(baseProps({
602
+ searchQuery: '판교로',
603
+ results: makeResults(PAGE_SIZE),
604
+ totalCount: LARGE_RESULT_COUNT
605
+ }));
606
+ // When: 본문을 아래로 스크롤하면
607
+ scrollContent(el, SCROLLED_OFFSET);
608
+ // Then: 경계선 클래스가 붙는다
609
+ (0, _vitest.expect)(isFixedSearchArea(el)).toBe(true);
610
+ // When: 다시 맨 위로 올리면
611
+ scrollContent(el, SCROLL_TOP);
612
+ // Then: 경계선 클래스가 사라진다
613
+ (0, _vitest.expect)(isFixedSearchArea(el)).toBe(false);
614
+ });
615
+ (0, _vitest.it)('결과가 없으면 스크롤이 일어나도 is-fixed 가 붙지 않는다', () => {
616
+ // Given: 초기 상태(결과 없음)로 렌더하고
617
+ const el = render(baseProps());
618
+ // When: 본문 스크롤 이벤트가 들어와도
619
+ scrollContent(el, SCROLLED_OFFSET);
620
+ // Then: 고정할 대상이 없으므로 클래스는 붙지 않는다
621
+ (0, _vitest.expect)(isFixedSearchArea(el)).toBe(false);
622
+ });
566
623
  });