@ncds/ui-admin 1.8.26-alpha.8 → 1.8.26

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.
@@ -30,17 +30,6 @@ export default defineConfig({
30
30
  // 'text' 미포함 — 미커버 파일이 수백 개라 파일별 상세 출력이 CI 로그를 덮는다.
31
31
  // 'lcov' 대신 'lcovonly' — 'lcov' 는 HTML 리포트까지 생성한다.
32
32
  reporter: ['text-summary', ['lcovonly', { projectRoot: REPO_ROOT }]],
33
- // 회귀 방지 하한선 — 실측에서 약 2%p 낮춘 값 (ui-admin-mcp 와 같은 여유폭).
34
- // 실측 기준: 2026-10-12, 25cb4d96 — stmts 54.91 / branch 44.63 / funcs 55.20 / lines 55.80
35
- // 최종 목표: 93 / 88 (lines 95 · branches 90). 테스트 보강 단계마다 실측 −2 로 인상한다.
36
- // autoUpdate: false — 값은 실측을 확인하고 사람이 직접 올린다.
37
- thresholds: {
38
- autoUpdate: false,
39
- statements: 53,
40
- branches: 43,
41
- functions: 53,
42
- lines: 54,
43
- },
44
33
  },
45
34
  },
46
35
  });
@@ -16,4 +16,4 @@ type BadgeProps = {
16
16
  };
17
17
  declare const Badge: ({ label, type, color, className, leadingIcon, trailingIcon, size, }: BadgeProps) => import("react/jsx-runtime").JSX.Element;
18
18
  export { Badge };
19
- export type { BadgeColor, BadgeProps, BadgeSize, BadgeType };
19
+ export type { BadgeType, BadgeColor, BadgeSize, BadgeProps };
@@ -67,6 +67,9 @@
67
67
  --shadow-xs-focused-3px-primary-100: 0px 1px 2px 0px #1018280d, 0px 0px 0px 3px #ffeaee;
68
68
  --shadow-xs-focused-3px-gray-100: 0px 1px 2px 0px #1018280d, 0px 0px 0px 3px #f2f4f7;
69
69
  --shadow-xs-focused-3px-error-100: 0px 1px 2px 0px #1018280d, 0px 0px 0px 3px #fee4e2;
70
+ --shadow-xs-focused-4px-primary-100: 0px 1px 2px 0px #1018280d, 0px 0px 0px 4px #ffeaee;
71
+ --shadow-xs-focused-4px-gray-100: 0px 1px 2px 0px #1018280d, 0px 0px 0px 4px #f2f4f7;
72
+ --shadow-xs-focused-4px-error-100: 0px 1px 2px 0px #1018280d, 0px 0px 0px 4px #fee4e2;
70
73
  --shadow-sm: 0px 1px 3px 0px #1018281a, 0px 1px 2px 0px #1018280f;
71
74
  --shadow-sm-focused-4px-primary-100: 0px 1px 2px 0px #1018280d, 0px 0px 0px 4px #ffeaee;
72
75
  --shadow-sm-focused-4px-gray-100: 0px 1px 3px 0px #1018281a, 0px 1px 2px 0px #1018280f, 0px 0px 0px 4px #f2f4f7;
@@ -324,7 +327,7 @@ button {
324
327
  background-color: var(--primary-red-450);
325
328
  border: 1px solid var(--primary-red-500);
326
329
  color: var(--base-white);
327
- box-shadow: var(--shadow-sm-focused-4px-primary-100);
330
+ box-shadow: var(--shadow-xs-focused-4px-primary-100);
328
331
  }
329
332
  .ncua-btn--primary:disabled, .ncua-btn--primary.is-disable {
330
333
  background-color: var(--primary-red-100);
@@ -370,7 +373,7 @@ button {
370
373
  background-color: var(--base-white);
371
374
  border: 1px solid var(--gray-200);
372
375
  color: var(--gray-700);
373
- box-shadow: var(--shadow-sm-focused-4px-gray-100);
376
+ box-shadow: var(--shadow-xs-focused-4px-gray-100);
374
377
  }
375
378
  .ncua-btn--secondary-gray:disabled, .ncua-btn--secondary-gray.is-disable {
376
379
  background-color: var(--base-white);
@@ -956,7 +959,7 @@ button {
956
959
  }
957
960
  .ncua-checkbox-input.destructive :where(input):focus {
958
961
  border-color: var(--primary-red-600);
959
- box-shadow: var(--shadow-sm-focused-4px-error-100);
962
+ box-shadow: var(--shadow-xs-focused-4px-error-100);
960
963
  }
961
964
  .ncua-checkbox-input:hover {
962
965
  border-color: var(--gray-600);
@@ -1741,7 +1744,7 @@ button {
1741
1744
  border-color: var(--primary-red-600);
1742
1745
  }
1743
1746
  .ncua-input.destructive:where(:focus-within) .ncua-input__field {
1744
- box-shadow: var(--shadow-sm-focused-4px-error-100);
1747
+ box-shadow: var(--shadow-xs-focused-4px-error-100);
1745
1748
  }
1746
1749
  .ncua-input.is-disabled .ncua-input__content > :not(button) {
1747
1750
  background-color: var(--gray-50);
@@ -1936,7 +1939,7 @@ button {
1936
1939
  border-color: var(--primary-red-600);
1937
1940
  }
1938
1941
  .ncua-input--textarea.destructive textarea:focus {
1939
- box-shadow: var(--shadow-sm-focused-4px-error-100);
1942
+ box-shadow: var(--shadow-xs-focused-4px-error-100);
1940
1943
  }
1941
1944
 
1942
1945
  .ncua-input--textarea--xs textarea {
@@ -3207,7 +3210,7 @@ button {
3207
3210
  }
3208
3211
  .ncua-radio-input.destructive :where(input):focus {
3209
3212
  border-color: var(--primary-red-600);
3210
- box-shadow: var(--shadow-sm-focused-4px-error-100);
3213
+ box-shadow: var(--shadow-xs-focused-4px-error-100);
3211
3214
  }
3212
3215
 
3213
3216
  .ncua-radio-field {
@@ -3709,7 +3712,7 @@ button {
3709
3712
  border-color: var(--primary-red-600);
3710
3713
  }
3711
3714
  .ncua-select.destructive .ncua-select__content:focus-within {
3712
- box-shadow: var(--shadow-sm-focused-4px-error-100);
3715
+ box-shadow: var(--shadow-xs-focused-4px-error-100);
3713
3716
  }
3714
3717
  .ncua-select--xs .ncua-select__content {
3715
3718
  height: var(--select-height-xs);
@@ -4050,7 +4053,7 @@ button {
4050
4053
  border-color: var(--primary-red-600);
4051
4054
  }
4052
4055
  .ncua-selectbox.destructive .ncua-selectbox__content:focus {
4053
- box-shadow: var(--shadow-sm-focused-4px-error-100);
4056
+ box-shadow: var(--shadow-xs-focused-4px-error-100);
4054
4057
  }
4055
4058
  .ncua-selectbox--xs .ncua-selectbox__content {
4056
4059
  height: var(--select-height-xs);
@@ -5353,16 +5356,15 @@ button {
5353
5356
  justify-content: center;
5354
5357
  color: var(--pink-600);
5355
5358
  background-color: var(--pink-200);
5359
+ border-radius: 5px;
5356
5360
  }
5357
5361
  .ncua-badge--new-badge-sm {
5358
5362
  width: 16px;
5359
5363
  height: 16px;
5360
- border-radius: 3px;
5361
5364
  }
5362
5365
  .ncua-badge--new-badge-md {
5363
5366
  width: 20px;
5364
5367
  height: 20px;
5365
- border-radius: 4px;
5366
5368
  }
5367
5369
 
5368
5370
  .ncua-badge-group {
@@ -6624,7 +6626,7 @@ button {
6624
6626
  bottom: 0;
6625
6627
  width: 3px;
6626
6628
  background: var(--base-black);
6627
- z-index: var(--z-index-1);
6629
+ z-index: 1;
6628
6630
  }
6629
6631
  .ncua-table__row--warning.ncua-table__row--selected:nth-child(n) {
6630
6632
  background: var(--orange-50);
@@ -6769,7 +6771,7 @@ button {
6769
6771
  .ncua-table--fixed-header .ncua-table__header-cell {
6770
6772
  position: sticky;
6771
6773
  top: 0;
6772
- z-index: var(--z-index-sticky);
6774
+ z-index: 10;
6773
6775
  padding: 0 var(--spacing-m);
6774
6776
  height: var(--ncua-table-header-height);
6775
6777
  }
@@ -7434,16 +7436,17 @@ button {
7434
7436
 
7435
7437
  .ncua-page-title {
7436
7438
  width: 100%;
7439
+ background: var(--base-white);
7440
+ box-shadow: var(--shadow-sm);
7437
7441
  }
7438
7442
  .ncua-page-title--fixed {
7439
7443
  position: sticky;
7440
7444
  top: 0;
7441
7445
  z-index: 999;
7442
- background: var(--gray-50);
7443
- border-bottom: 1px solid var(--gray-100);
7446
+ border-bottom: 1px solid var(--gray-300);
7444
7447
  }
7445
7448
  .ncua-page-title__page-header {
7446
- padding: 32px 40px 0;
7449
+ padding: 24px 40px;
7447
7450
  }
7448
7451
  .ncua-page-title--fixed .ncua-page-title__page-header {
7449
7452
  padding: 12px 40px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ncds/ui-admin",
3
- "version": "1.8.26-alpha.8",
3
+ "version": "1.8.26",
4
4
  "description": "nhn-commerce의 어드민 디자인 시스템입니다.",
5
5
  "scripts": {
6
6
  "barrel": "node barrel.js",
@@ -1,94 +0,0 @@
1
- "use strict";
2
-
3
- var _nodePath = _interopRequireDefault(require("node:path"));
4
- var _sass = require("sass");
5
- var _vitest = require("vitest");
6
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
- // 배경·그림자·패딩은 jsdom 에서 계산되지 않으므로, 컴파일된 CSS 에서 variant 별 규칙을 검사한다.
8
- // (input-base/__tests__/inputAutofill.test.ts 와 같은 방식 — 컴포넌트 SCSS 엔트리를 직접 컴파일)
9
- const PAGE_TITLE_SCSS_PATH = _nodePath.default.resolve(__dirname, '../../../../../assets/styles/components/page-title/_index.scss');
10
- const compiledPageTitleCss = (0, _sass.compile)(PAGE_TITLE_SCSS_PATH).css;
11
- /** 선택자 → 선언 목록. 같은 선택자가 여러 규칙에 나오면 선언을 이어 붙인다. */
12
- const parseRules = css => {
13
- const rules = new Map();
14
- for (const [, selectorList, body] of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
15
- const declarations = body.split(';').map(declaration => declaration.trim().replace(/\s+/g, ' ')).filter(Boolean);
16
- for (const selector of selectorList.split(',').map(selector => selector.trim().replace(/\s+/g, ' '))) {
17
- rules.set(selector, [...(rules.get(selector) ?? []), ...declarations]);
18
- }
19
- }
20
- return rules;
21
- };
22
- const rules = parseRules(compiledPageTitleCss);
23
- const declarationsOf = selector => rules.get(selector) ?? [];
24
- const propertiesOf = selector => declarationsOf(selector).map(declaration => declaration.split(':')[0].trim());
25
- const ROOT = '.ncua-page-title';
26
- const FIXED = '.ncua-page-title--fixed';
27
- const PAGE_HEADER = '.ncua-page-title__page-header';
28
- const FIXED_PAGE_HEADER = '.ncua-page-title--fixed .ncua-page-title__page-header';
29
- (0, _vitest.describe)('PageTitle 스타일 — Default (·Detail)', () => {
30
- (0, _vitest.it)('.ncua-page-title 에 background·box-shadow 가 없다 (투명, 그림자 없음)', () => {
31
- // Given: 컴파일된 PageTitle CSS 에서
32
- // When: 루트 규칙의 속성을 모으면
33
- const properties = propertiesOf(ROOT);
34
- // Then: 루트 규칙은 존재하지만 배경·그림자를 지정하지 않는다
35
- (0, _vitest.expect)(properties.length).toBeGreaterThan(0);
36
- (0, _vitest.expect)(properties).not.toContain('background');
37
- (0, _vitest.expect)(properties).not.toContain('background-color');
38
- (0, _vitest.expect)(properties).not.toContain('box-shadow');
39
- });
40
- (0, _vitest.it)('.ncua-page-title__page-header 패딩은 32px 40px 0 이다', () => {
41
- // Given: 컴파일된 PageTitle CSS 에서
42
- // When: page-header 규칙을 찾으면
43
- const declarations = declarationsOf(PAGE_HEADER);
44
- // Then: 위 32px · 좌우 40px · 아래 0 이다
45
- (0, _vitest.expect)(declarations).toContain('padding: 32px 40px 0');
46
- (0, _vitest.expect)(declarations).not.toContain('padding: 24px 40px');
47
- });
48
- });
49
- (0, _vitest.describe)('PageTitle 스타일 — Fixed (·Fixed Detail)', () => {
50
- (0, _vitest.it)('.ncua-page-title--fixed 배경은 --gray-50 이다', () => {
51
- // Given: 컴파일된 PageTitle CSS 에서
52
- // When: fixed modifier 규칙을 찾으면
53
- const declarations = declarationsOf(FIXED);
54
- // Then: sticky 로 콘텐츠 위에 겹치므로 배경 Gray/50 을 가진다
55
- (0, _vitest.expect)(declarations).toContain('background: var(--gray-50)');
56
- });
57
- (0, _vitest.it)('.ncua-page-title--fixed 하단 border 는 1px solid --gray-100 이다', () => {
58
- // Given/When: fixed modifier 규칙을 찾으면
59
- const declarations = declarationsOf(FIXED);
60
- // Then: 기존 Gray/300 이 아니라 Gray/100 border 를 쓴다
61
- (0, _vitest.expect)(declarations).toContain('border-bottom: 1px solid var(--gray-100)');
62
- (0, _vitest.expect)(declarations).not.toContain('border-bottom: 1px solid var(--gray-300)');
63
- });
64
- (0, _vitest.it)('.ncua-page-title--fixed 에 box-shadow 가 없다', () => {
65
- // Given/When: fixed modifier 규칙의 속성을 모으면
66
- // Then: 그림자를 지정하지 않는다
67
- (0, _vitest.expect)(propertiesOf(FIXED)).not.toContain('box-shadow');
68
- });
69
- (0, _vitest.it)('Fixed 의 page-header 패딩은 12px 40px 로 유지된다', () => {
70
- // Given/When: fixed 하위 page-header 규칙을 찾으면
71
- // Then: 기존 Fixed 패딩이 그대로다 (범위 밖)
72
- (0, _vitest.expect)(declarationsOf(FIXED_PAGE_HEADER)).toContain('padding: 12px 40px');
73
- });
74
- (0, _vitest.it)('Fixed sticky 동작(position·top·z-index)은 유지된다', () => {
75
- // Given/When: fixed modifier 규칙을 찾으면
76
- const declarations = declarationsOf(FIXED);
77
- // Then: 기존 sticky 선언이 그대로다
78
- (0, _vitest.expect)(declarations).toContain('position: sticky');
79
- (0, _vitest.expect)(declarations).toContain('top: 0');
80
- (0, _vitest.expect)(declarations).toContain('z-index: 999');
81
- });
82
- });
83
- (0, _vitest.describe)('PageTitle 스타일 — 토큰 사용', () => {
84
- (0, _vitest.it)('배경·border 색상에 하드코딩 값 없이 CSS 변수만 쓴다 (ADR-4)', () => {
85
- // Given/When: 루트·fixed 규칙의 배경·border 선언을 모으면
86
- const colorDeclarations = [...declarationsOf(ROOT), ...declarationsOf(FIXED)].filter(declaration => /^(background|border-bottom)/.test(declaration));
87
- // Then: hex·rgb·색 이름 대신 var(--*) 만 쓴다
88
- (0, _vitest.expect)(colorDeclarations.length).toBeGreaterThan(0);
89
- for (const declaration of colorDeclarations) {
90
- (0, _vitest.expect)(declaration).not.toMatch(/#[0-9a-f]{3,8}\b|rgba?\(|(?<![-\w])white\b/i);
91
- (0, _vitest.expect)(declaration).toMatch(/var\(--[a-z0-9-]+\)/);
92
- }
93
- });
94
- });
@@ -1,91 +0,0 @@
1
- import path from 'node:path';
2
- import { compile as compileSass } from 'sass';
3
- import { describe, expect, it } from 'vitest';
4
- // 배경·그림자·패딩은 jsdom 에서 계산되지 않으므로, 컴파일된 CSS 에서 variant 별 규칙을 검사한다.
5
- // (input-base/__tests__/inputAutofill.test.ts 와 같은 방식 — 컴포넌트 SCSS 엔트리를 직접 컴파일)
6
- const PAGE_TITLE_SCSS_PATH = path.resolve(__dirname, '../../../../../assets/styles/components/page-title/_index.scss');
7
- const compiledPageTitleCss = compileSass(PAGE_TITLE_SCSS_PATH).css;
8
- /** 선택자 → 선언 목록. 같은 선택자가 여러 규칙에 나오면 선언을 이어 붙인다. */
9
- const parseRules = css => {
10
- const rules = new Map();
11
- for (const [, selectorList, body] of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
12
- const declarations = body.split(';').map(declaration => declaration.trim().replace(/\s+/g, ' ')).filter(Boolean);
13
- for (const selector of selectorList.split(',').map(selector => selector.trim().replace(/\s+/g, ' '))) {
14
- rules.set(selector, [...(rules.get(selector) ?? []), ...declarations]);
15
- }
16
- }
17
- return rules;
18
- };
19
- const rules = parseRules(compiledPageTitleCss);
20
- const declarationsOf = selector => rules.get(selector) ?? [];
21
- const propertiesOf = selector => declarationsOf(selector).map(declaration => declaration.split(':')[0].trim());
22
- const ROOT = '.ncua-page-title';
23
- const FIXED = '.ncua-page-title--fixed';
24
- const PAGE_HEADER = '.ncua-page-title__page-header';
25
- const FIXED_PAGE_HEADER = '.ncua-page-title--fixed .ncua-page-title__page-header';
26
- describe('PageTitle 스타일 — Default (·Detail)', () => {
27
- it('.ncua-page-title 에 background·box-shadow 가 없다 (투명, 그림자 없음)', () => {
28
- // Given: 컴파일된 PageTitle CSS 에서
29
- // When: 루트 규칙의 속성을 모으면
30
- const properties = propertiesOf(ROOT);
31
- // Then: 루트 규칙은 존재하지만 배경·그림자를 지정하지 않는다
32
- expect(properties.length).toBeGreaterThan(0);
33
- expect(properties).not.toContain('background');
34
- expect(properties).not.toContain('background-color');
35
- expect(properties).not.toContain('box-shadow');
36
- });
37
- it('.ncua-page-title__page-header 패딩은 32px 40px 0 이다', () => {
38
- // Given: 컴파일된 PageTitle CSS 에서
39
- // When: page-header 규칙을 찾으면
40
- const declarations = declarationsOf(PAGE_HEADER);
41
- // Then: 위 32px · 좌우 40px · 아래 0 이다
42
- expect(declarations).toContain('padding: 32px 40px 0');
43
- expect(declarations).not.toContain('padding: 24px 40px');
44
- });
45
- });
46
- describe('PageTitle 스타일 — Fixed (·Fixed Detail)', () => {
47
- it('.ncua-page-title--fixed 배경은 --gray-50 이다', () => {
48
- // Given: 컴파일된 PageTitle CSS 에서
49
- // When: fixed modifier 규칙을 찾으면
50
- const declarations = declarationsOf(FIXED);
51
- // Then: sticky 로 콘텐츠 위에 겹치므로 배경 Gray/50 을 가진다
52
- expect(declarations).toContain('background: var(--gray-50)');
53
- });
54
- it('.ncua-page-title--fixed 하단 border 는 1px solid --gray-100 이다', () => {
55
- // Given/When: fixed modifier 규칙을 찾으면
56
- const declarations = declarationsOf(FIXED);
57
- // Then: 기존 Gray/300 이 아니라 Gray/100 border 를 쓴다
58
- expect(declarations).toContain('border-bottom: 1px solid var(--gray-100)');
59
- expect(declarations).not.toContain('border-bottom: 1px solid var(--gray-300)');
60
- });
61
- it('.ncua-page-title--fixed 에 box-shadow 가 없다', () => {
62
- // Given/When: fixed modifier 규칙의 속성을 모으면
63
- // Then: 그림자를 지정하지 않는다
64
- expect(propertiesOf(FIXED)).not.toContain('box-shadow');
65
- });
66
- it('Fixed 의 page-header 패딩은 12px 40px 로 유지된다', () => {
67
- // Given/When: fixed 하위 page-header 규칙을 찾으면
68
- // Then: 기존 Fixed 패딩이 그대로다 (범위 밖)
69
- expect(declarationsOf(FIXED_PAGE_HEADER)).toContain('padding: 12px 40px');
70
- });
71
- it('Fixed sticky 동작(position·top·z-index)은 유지된다', () => {
72
- // Given/When: fixed modifier 규칙을 찾으면
73
- const declarations = declarationsOf(FIXED);
74
- // Then: 기존 sticky 선언이 그대로다
75
- expect(declarations).toContain('position: sticky');
76
- expect(declarations).toContain('top: 0');
77
- expect(declarations).toContain('z-index: 999');
78
- });
79
- });
80
- describe('PageTitle 스타일 — 토큰 사용', () => {
81
- it('배경·border 색상에 하드코딩 값 없이 CSS 변수만 쓴다 (ADR-4)', () => {
82
- // Given/When: 루트·fixed 규칙의 배경·border 선언을 모으면
83
- const colorDeclarations = [...declarationsOf(ROOT), ...declarationsOf(FIXED)].filter(declaration => /^(background|border-bottom)/.test(declaration));
84
- // Then: hex·rgb·색 이름 대신 var(--*) 만 쓴다
85
- expect(colorDeclarations.length).toBeGreaterThan(0);
86
- for (const declaration of colorDeclarations) {
87
- expect(declaration).not.toMatch(/#[0-9a-f]{3,8}\b|rgba?\(|(?<![-\w])white\b/i);
88
- expect(declaration).toMatch(/var\(--[a-z0-9-]+\)/);
89
- }
90
- });
91
- });
@@ -1,94 +0,0 @@
1
- import path from 'node:path';
2
- import { compile as compileSass } from 'sass';
3
- import { describe, expect, it } from 'vitest';
4
- // 배경·그림자·패딩은 jsdom 에서 계산되지 않으므로, 컴파일된 CSS 에서 variant 별 규칙을 검사한다.
5
- // (input-base/__tests__/inputAutofill.test.ts 와 같은 방식 — 컴포넌트 SCSS 엔트리를 직접 컴파일)
6
- const PAGE_TITLE_SCSS_PATH = path.resolve(__dirname, '../../../../../assets/styles/components/page-title/_index.scss');
7
- const compiledPageTitleCss = compileSass(PAGE_TITLE_SCSS_PATH).css;
8
- /** 선택자 → 선언 목록. 같은 선택자가 여러 규칙에 나오면 선언을 이어 붙인다. */
9
- const parseRules = (css) => {
10
- const rules = new Map();
11
- for (const [, selectorList, body] of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
12
- const declarations = body
13
- .split(';')
14
- .map((declaration) => declaration.trim().replace(/\s+/g, ' '))
15
- .filter(Boolean);
16
- for (const selector of selectorList.split(',').map((selector) => selector.trim().replace(/\s+/g, ' '))) {
17
- rules.set(selector, [...(rules.get(selector) ?? []), ...declarations]);
18
- }
19
- }
20
- return rules;
21
- };
22
- const rules = parseRules(compiledPageTitleCss);
23
- const declarationsOf = (selector) => rules.get(selector) ?? [];
24
- const propertiesOf = (selector) => declarationsOf(selector).map((declaration) => declaration.split(':')[0].trim());
25
- const ROOT = '.ncua-page-title';
26
- const FIXED = '.ncua-page-title--fixed';
27
- const PAGE_HEADER = '.ncua-page-title__page-header';
28
- const FIXED_PAGE_HEADER = '.ncua-page-title--fixed .ncua-page-title__page-header';
29
- describe('PageTitle 스타일 — Default (·Detail)', () => {
30
- it('.ncua-page-title 에 background·box-shadow 가 없다 (투명, 그림자 없음)', () => {
31
- // Given: 컴파일된 PageTitle CSS 에서
32
- // When: 루트 규칙의 속성을 모으면
33
- const properties = propertiesOf(ROOT);
34
- // Then: 루트 규칙은 존재하지만 배경·그림자를 지정하지 않는다
35
- expect(properties.length).toBeGreaterThan(0);
36
- expect(properties).not.toContain('background');
37
- expect(properties).not.toContain('background-color');
38
- expect(properties).not.toContain('box-shadow');
39
- });
40
- it('.ncua-page-title__page-header 패딩은 32px 40px 0 이다', () => {
41
- // Given: 컴파일된 PageTitle CSS 에서
42
- // When: page-header 규칙을 찾으면
43
- const declarations = declarationsOf(PAGE_HEADER);
44
- // Then: 위 32px · 좌우 40px · 아래 0 이다
45
- expect(declarations).toContain('padding: 32px 40px 0');
46
- expect(declarations).not.toContain('padding: 24px 40px');
47
- });
48
- });
49
- describe('PageTitle 스타일 — Fixed (·Fixed Detail)', () => {
50
- it('.ncua-page-title--fixed 배경은 --gray-50 이다', () => {
51
- // Given: 컴파일된 PageTitle CSS 에서
52
- // When: fixed modifier 규칙을 찾으면
53
- const declarations = declarationsOf(FIXED);
54
- // Then: sticky 로 콘텐츠 위에 겹치므로 배경 Gray/50 을 가진다
55
- expect(declarations).toContain('background: var(--gray-50)');
56
- });
57
- it('.ncua-page-title--fixed 하단 border 는 1px solid --gray-100 이다', () => {
58
- // Given/When: fixed modifier 규칙을 찾으면
59
- const declarations = declarationsOf(FIXED);
60
- // Then: 기존 Gray/300 이 아니라 Gray/100 border 를 쓴다
61
- expect(declarations).toContain('border-bottom: 1px solid var(--gray-100)');
62
- expect(declarations).not.toContain('border-bottom: 1px solid var(--gray-300)');
63
- });
64
- it('.ncua-page-title--fixed 에 box-shadow 가 없다', () => {
65
- // Given/When: fixed modifier 규칙의 속성을 모으면
66
- // Then: 그림자를 지정하지 않는다
67
- expect(propertiesOf(FIXED)).not.toContain('box-shadow');
68
- });
69
- it('Fixed 의 page-header 패딩은 12px 40px 로 유지된다', () => {
70
- // Given/When: fixed 하위 page-header 규칙을 찾으면
71
- // Then: 기존 Fixed 패딩이 그대로다 (범위 밖)
72
- expect(declarationsOf(FIXED_PAGE_HEADER)).toContain('padding: 12px 40px');
73
- });
74
- it('Fixed sticky 동작(position·top·z-index)은 유지된다', () => {
75
- // Given/When: fixed modifier 규칙을 찾으면
76
- const declarations = declarationsOf(FIXED);
77
- // Then: 기존 sticky 선언이 그대로다
78
- expect(declarations).toContain('position: sticky');
79
- expect(declarations).toContain('top: 0');
80
- expect(declarations).toContain('z-index: 999');
81
- });
82
- });
83
- describe('PageTitle 스타일 — 토큰 사용', () => {
84
- it('배경·border 색상에 하드코딩 값 없이 CSS 변수만 쓴다 (ADR-4)', () => {
85
- // Given/When: 루트·fixed 규칙의 배경·border 선언을 모으면
86
- const colorDeclarations = [...declarationsOf(ROOT), ...declarationsOf(FIXED)].filter((declaration) => /^(background|border-bottom)/.test(declaration));
87
- // Then: hex·rgb·색 이름 대신 var(--*) 만 쓴다
88
- expect(colorDeclarations.length).toBeGreaterThan(0);
89
- for (const declaration of colorDeclarations) {
90
- expect(declaration).not.toMatch(/#[0-9a-f]{3,8}\b|rgba?\(|(?<![-\w])white\b/i);
91
- expect(declaration).toMatch(/var\(--[a-z0-9-]+\)/);
92
- }
93
- });
94
- });