@ncds/ui-admin 1.8.26-alpha.9 → 1.8.27
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/generated/scoped-css.js +1 -1
- package/dist/cjs/vitest.config.js +1 -12
- package/dist/esm/src/components/feedback-and-status/badge/Badge.js +1 -1
- package/dist/esm/src/generated/scoped-css.js +1 -1
- package/dist/esm/vitest.config.js +1 -12
- 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/generated/scoped-css.js +1 -1
- package/dist/temp/vitest.config.js +0 -11
- package/dist/types/src/components/feedback-and-status/badge/Badge.d.ts +1 -1
- package/dist/ui-admin/assets/styles/style.css +18 -15
- package/package.json +1 -1
- package/dist/cjs/src/components/layout/page-title/__tests__/pageTitleStyle.test.js +0 -94
- package/dist/esm/src/components/layout/page-title/__tests__/pageTitleStyle.test.js +0 -91
- package/dist/temp/src/components/layout/page-title/__tests__/pageTitleStyle.test.d.ts +0 -1
- package/dist/temp/src/components/layout/page-title/__tests__/pageTitleStyle.test.js +0 -94
- package/dist/types/src/components/layout/page-title/__tests__/pageTitleStyle.test.d.ts +0 -1
|
@@ -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 {
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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,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 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -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
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|