fix(sirsoft-gdpr): 관리자 설정 카드 제목 경고 색상 오용 수정

가 GDPR 관리자 설정 화면 스크린샷에서 카드 제목만 다른 확장과 달리
빨간색으로 표시되는 것을 발견해 지시했다.

「운영 주체」·「쿠키 동의 배너」·「자동 차단 정책」 카드 제목과 차단
도메인 안내 소제목이 에러/경고 전용 시맨틱(text-error-strong, red 계열)을
잘못 사용하고 있었다. 일반 카드 제목이므로 다른 정상 카드와 동일한
중립 톤(section-heading-md)으로 교체.

원래 develop 에 이미 있던 기존 결함이며 다른 확장(marketing,
pay_kginicis 등)에도 동일 패턴이 있으나, 이번엔 GDPR 플러그인 범위로
한정해 수정한다.

기존 Vitest 회귀 가드가 이 색상 버그를 "의도된 설계"로 잘못 확정해두고
있어 함께 뒤집었고, 실제 브라우저에서 색상을 검증하는 E2E 테스트도
추가했다(computed style RGB 값 검사).
This commit is contained in:
chym1217
2026-07-30 10:50:50 +09:00
parent 11d3cc6445
commit 4d50fa2cde
4 changed files with 66 additions and 10 deletions
@@ -33,6 +33,7 @@
- 관리자 환경설정의 「운영 주체」·「쿠키 배너 노출」·「배너 위치」 필드에서 안내 문구가 길어질 때 입력·토글 컨트롤이 옆이 아닌 아래 줄로 밀려나던 문제를 바로잡았습니다.
- 비회원(게스트) 방문자의 쿠키 동의 식별자가 위·변조에 노출되던 문제를 바로잡았습니다 — 다른 방문자의 쿠키 동의 상태를 임의로 조회·변경할 수 없도록 서명을 적용했습니다. 이 조치로 이번 업데이트 이전에 발급된 비회원 쿠키 배너 상태는 1회에 한해 초기화되며, 이미 배너를 닫았던 비회원 방문자는 다음 방문 시 배너를 다시 보게 됩니다(회원 동의 이력은 영향 없음).
- 비회원으로 쿠키 배너를 닫은 뒤 동의 이력이 없는 다른 계정으로 로그인하면 배너가 계속 숨겨져 있던 문제를 바로잡았습니다 — 이제 로그인한 계정 기준으로 다시 판단해 배너가 정상적으로 노출됩니다.
- 관리자 환경설정의 「운영 주체」·「쿠키 동의 배너」·「자동 차단 정책」 카드 제목과 차단 도메인 안내 문구가 빨간색(경고 색상)으로 표시되던 문제를 바로잡았습니다 — 일반 안내에도 경고 색상이 쓰여 어색하게 강조되던 부분을 다른 관리자 화면과 동일한 색상으로 정리했습니다.
## [1.0.1] - 2026-07-16
@@ -144,9 +144,37 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
expect(className).toContain('grid-3col-responsive');
});
it('section-heading-md 는 더 이상 필드 2단 배치 컨테이너로 오용되지 않는다 (텍스트 스타일 전용 클래스)', () => {
const layoutJson = JSON.stringify(root);
expect(layoutJson).not.toContain('"className":"section-heading-md"');
it('section-heading-md 는 필드 2단 배치 컨테이너로 오용되지 않는다 (해당 4개 필드 컨테이너 한정 검증)', () => {
// 카드 제목(H3)은 section-heading-md 를 정당하게 사용하므로(카드 제목 색상
// 수정 회귀 가드 참조), 전체 레이아웃 문자열이 아니라 필드 컨테이너 4개만 검증한다.
for (const fieldId of ['field_legal_entity_name', 'field_data_storage_location', 'field_banner_enabled', 'field_banner_position']) {
const field = findById(root, fieldId);
const className = String((field?.props as { className?: string } | undefined)?.className ?? '');
expect(className).not.toBe('section-heading-md');
}
});
});
/**
* 카드 제목(H3) 색상 회귀 가드 — text-error-strong(에러/경고 전용 시맨틱, red 계열)이
* 일반 카드 제목에 오용되어 "운영 주체", "쿠키 동의 배너", "자동 차단 정책" 제목이 빨간색으로
* 표시되던 결함. 정상 카드 제목 색상은 section-heading-md(중립 톤).
*/
describe('카드 제목 색상 — text-error-strong 오용 회귀 가드', () => {
it.each(['card_operator_header', 'card_cookie_banner_header', 'card_auto_blocking_policy_header'])(
'%s 의 H3 제목이 section-heading-md 를 사용한다 (에러 색상 아님)',
(headerId) => {
const header = findById(root, headerId);
const json = JSON.stringify(header);
expect(json).toContain('"className":"section-heading-md"');
expect(json).not.toContain('text-error-strong');
}
);
it('차단 도메인 안내 박스(정보 톤)의 소제목도 에러 색상을 사용하지 않는다', () => {
const box = findById(root, 'blocked_domains_warnings_box');
const json = JSON.stringify(box);
expect(json).not.toContain('text-error-strong');
});
});
@@ -350,8 +378,10 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
expect(serialized).not.toContain('blocked_domains.preblocker_active');
expect(serialized).not.toContain('blocked_domains.self_hosted_attr');
expect(serialized).not.toContain('blocked_domains.static_html_limitation');
// 타이틀 강조 — 시맨틱 text-error-strong 로 제목 강조 (#399 시맨틱 통일)
expect(serialized).toContain('text-error-strong');
// 타이틀 — 안내 박스(정보 톤, circle-info)의 소제목이므로 에러 강조색이 아닌 중립 톤 사용
// (text-error-strong 은 에러/경고 전용 시맨틱이며 일반 안내 제목에 오용되던 결함을 바로잡음)
expect(serialized).not.toContain('text-error-strong');
expect(serialized).toContain('text-gray-900');
// 옛 badge 키는 lang 과 박스에서 모두 제거됨
expect(serialized).not.toContain('blocked_domains.warnings_badge');
// 기존 옛 항목 키 미사용
@@ -259,7 +259,7 @@
"type": "basic",
"name": "H3",
"props": {
"className": "text-error-strong"
"className": "section-heading-md"
},
"text": "$t:sirsoft-gdpr.settings.section.operator"
},
@@ -502,7 +502,7 @@
"type": "basic",
"name": "H3",
"props": {
"className": "text-error-strong"
"className": "section-heading-md"
},
"text": "$t:sirsoft-gdpr.settings.section.cookie_banner"
},
@@ -1541,7 +1541,7 @@
"type": "basic",
"name": "H3",
"props": {
"className": "text-error-strong"
"className": "section-heading-md"
},
"text": "$t:sirsoft-gdpr.settings.section.auto_blocking"
},
@@ -1641,7 +1641,7 @@
"type": "basic",
"name": "H4",
"props": {
"className": "text-error-strong"
"className": "text-sm font-medium text-gray-900 dark:text-white"
},
"text": "$t:sirsoft-gdpr.settings.fields.blocked_domains.warnings_title"
}
@@ -1,8 +1,9 @@
/**
* E2E: 관리자 환경설정 > 쿠키 배너 > 정책 버전 > 보기 — 본문 페이지 링크 (#509 체크리스트 12번)
* + 관리자 환경설정 카드 제목 색상 회귀 가드 (PO 스크린샷 발견)
*
* @scenario admin_gdpr_policy_version_snapshot_open_policy_page_link
* @effects snapshot_modal_opens, policy_page_link_href_matches_page_module_url_pattern
* @effects snapshot_modal_opens, policy_page_link_href_matches_page_module_url_pattern, card_title_not_error_color
*
* 배경: 정책 버전 snapshot 모달의 본문 페이지 링크가 `/{{slug}}` 로 조합되어 페이지 모듈
* (sirsoft-page) 의 실제 URL 패턴 `/page/{slug}` 와 불일치, 클릭 시 404 가 발생하던 결함.
@@ -10,16 +11,22 @@
* 포함되므로 (GdprPolicyVersionMigrationSmokeTest 참조), 별도 설정 저장 없이도 v배지 클릭 →
* 모달의 본문 페이지 링크 href 를 검증할 수 있다.
*
* 또한 카드 제목(H3)이 에러/경고 전용 시맨틱(text-error-strong, red 계열)을 오용해
* "운영 주체" 등 일반 카드 제목이 빨간색으로 표시되던 결함을 PO 가 스크린샷으로 발견해
* section-heading-md(중립 톤)로 수정 — 실제 렌더링된 색상을 브라우저에서 검증한다.
*
* 검증:
* 1. 환경설정 화면에서 v배지 옆 "본문 보기" 버튼 클릭 시 snapshot 모달이 열린다
* 2. 모달의 본문 페이지 링크 href 가 `/page/{slug}` 형식으로 조합되어 페이지 모듈 URL 패턴과 일치한다
* (프리픽스 누락으로 `/{slug}` 형태가 되는 결함 패턴이 재발하지 않았는지 회귀 가드)
* 3. 「운영 주체 / 개인정보처리방침」 카드 제목이 에러 색상(red 계열)이 아닌 중립 톤으로 렌더링된다
*/
import { test, expect, authenticatePage } from '../../fixtures/gdpr-auth';
const CARD_COOKIE_BANNER = '#card_cookie_banner';
const SNAPSHOT_VIEW_BUTTON = '#policy_version_current_snapshot_view_button';
const OPEN_POLICY_PAGE_LINK = '#policy_version_snapshot_open_policy_page_link';
const CARD_OPERATOR_HEADER = '#card_operator_header h3';
/** 관리자 GDPR 환경설정 진입 후 쿠키 배너 탭(정책 버전 UI 포함)으로 스크롤 이동 */
async function gotoGdprSettings(page: import('@playwright/test').Page): Promise<void> {
@@ -51,3 +58,21 @@ test('#509 - 정책 버전 snapshot 모달의 본문 페이지 링크가 /page/{
expect(href).toMatch(/^\/page\/.+/);
expect(href).not.toMatch(/^\/(?!page\/)/);
});
// @scenario tab=card_operator, permitted=yes
// @effects card_title_not_error_color
test('#509 - 관리자 환경설정 카드 제목이 에러 색상(빨간색)으로 표시되지 않는다', async ({ page, privacyManageToken }) => {
await authenticatePage(page, privacyManageToken);
await gotoGdprSettings(page);
const header = page.locator(CARD_OPERATOR_HEADER);
await expect(header).toBeVisible({ timeout: 10_000 });
const color = await header.evaluate((el) => getComputedStyle(el).color);
// text-error-strong(text-red-800/dark:text-red-200) 이 남아있다면 rgb 값의 R 채널이
// G/B 채널보다 뚜렷이 높은 붉은 계열로 계산된다. 정상(중립 톤)은 R≈G≈B.
const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
expect(rgbMatch).not.toBeNull();
const [, r, g, b] = rgbMatch as unknown as [string, string, string, string];
expect(Number(r) - Math.max(Number(g), Number(b))).toBeLessThan(40);
});