test: expand exporter/css/a11y/i18n regressions and add e2e skip-link focus check
CI / test (pull_request) Successful in 52s

This commit is contained in:
2025-11-14 20:10:50 +09:00
parent eb28c9fcc6
commit 02637a8b52
27 changed files with 1045 additions and 19 deletions
+2 -1
View File
@@ -13,7 +13,8 @@ export default function I18nProvider({
children: React.ReactNode
}) {
return (
<NextIntlClientProvider locale={locale} messages={messages}>
// next-intl: Provide a default timeZone to avoid ENVIRONMENT_FALLBACK mismatches
<NextIntlClientProvider locale={locale} messages={messages} timeZone="Asia/Seoul">
{children}
</NextIntlClientProvider>
)
@@ -0,0 +1,53 @@
import { render, screen, fireEvent } from '@testing-library/react'
import '@testing-library/jest-dom'
import React from 'react'
import BuilderClientPage from '@/components/BuilderClientPage'
import I18nProvider from '@/components/I18nProvider'
import en from '@/messages/en.json'
import { assertDescribedBy } from '@/components/testUtils/inspectorA11y'
describe('Inspector a11y sections template - Hero/CTA/FAQ describedby wiring', () => {
it('Hero, CTA, FAQ inspector inputs are wired to helptexts', () => {
render(
<I18nProvider locale="en" messages={en as unknown as Record<string, string>}>
<BuilderClientPage />
</I18nProvider>
)
// Add hero to enable inspector
const addHero = screen.getByRole('button', { name: 'Add Hero' })
fireEvent.click(addHero)
// Select first section (Hero)
const selectButtons = screen.getAllByRole('button', { name: /Select \d+/ })
fireEvent.click(selectButtons[0])
const heroHeading = screen.getByLabelText('Hero Heading')
assertDescribedBy(heroHeading, 'ins-hero-heading-help')
// Replace with CTA and test describedby
const addCta = screen.getByRole('button', { name: 'Add CTA' })
fireEvent.click(addCta)
const selectSecond = screen.getAllByRole('button', { name: /Select \d+/ })[1]
fireEvent.click(selectSecond)
const ctaText = screen.getByLabelText('CTA Text')
assertDescribedBy(ctaText, 'ins-cta-text-help')
const ctaHref = screen.getByLabelText('CTA Href')
assertDescribedBy(ctaHref, 'ins-cta-href-help')
// Add FAQ and test using shared help id
const addFaq = screen.getByRole('button', { name: 'Add FAQ' })
fireEvent.click(addFaq)
const selectThird = screen.getAllByRole('button', { name: /Select \d+/ })[2]
fireEvent.click(selectThird)
// Add one FAQ item to render inputs
const addFaqItem = screen.getByRole('button', { name: 'Add FAQ Item' })
fireEvent.click(addFaqItem)
const faqQ = screen.getByLabelText('FAQ Q 0')
const faqA = screen.getByLabelText('FAQ A 0')
assertDescribedBy(faqQ, 'ins-faq-help')
assertDescribedBy(faqA, 'ins-faq-help')
})
})
@@ -0,0 +1,29 @@
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import React from 'react'
import BuilderClientPage from '@/components/BuilderClientPage'
import I18nProvider from '@/components/I18nProvider'
import en from '@/messages/en.json'
import { assertDescribedBy } from '@/components/testUtils/inspectorA11y'
describe('Inspector a11y template - describedby wiring', () => {
it('SEO/Analytics inputs are wired to their helptexts via aria-describedby', () => {
render(
<I18nProvider locale="en" messages={en as unknown as Record<string, string>}>
<BuilderClientPage />
</I18nProvider>
)
const seoTitle = screen.getByLabelText('SEO Title')
const seoDesc = screen.getByLabelText('SEO Description')
const ogImage = screen.getByLabelText('OG Image URL')
const ga4 = screen.getByLabelText('GA4 Measurement ID')
const meta = screen.getByLabelText('Meta Pixel ID')
assertDescribedBy(seoTitle, 'ins-seo-title-help')
assertDescribedBy(seoDesc, 'ins-seo-desc-help')
assertDescribedBy(ogImage, 'ins-og-image-help')
assertDescribedBy(ga4, 'ins-ga4-help')
assertDescribedBy(meta, 'ins-meta-pixel-help')
})
})
+28
View File
@@ -0,0 +1,28 @@
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import React from 'react'
import BuilderClientPage from '@/components/BuilderClientPage'
import I18nProvider from '@/components/I18nProvider'
import en from '@/messages/en.json'
describe('Buttons naming/aria-label consistency (smoke)', () => {
it('renders primary action buttons with stable accessible names', () => {
render(
<I18nProvider locale="en" messages={en as unknown as Record<string, string>}>
<BuilderClientPage />
</I18nProvider>
)
// Sidebar/toolbar actions
expect(screen.getByRole('button', { name: 'Add Hero' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add FAQ' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add CTA' })).toBeInTheDocument()
// Export button
expect(screen.getByRole('button', { name: 'Export ZIP' })).toBeInTheDocument()
// Preview viewport toggle buttons have explicit aria-labels as names
expect(screen.getByRole('button', { name: 'Desktop' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Mobile' })).toBeInTheDocument()
})
})
+42
View File
@@ -0,0 +1,42 @@
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import React from 'react'
import BuilderClientPage from '@/components/BuilderClientPage'
import I18nProvider from '@/components/I18nProvider'
import en from '@/messages/en.json'
import ko from '@/messages/ko.json'
describe('i18n regression - form add actions (en/ko)', () => {
function renderWith(locale: 'en' | 'ko') {
const messages = locale === 'en' ? en : ko
return render(
<I18nProvider locale={locale} messages={messages as unknown as Record<string, string>}>
<BuilderClientPage />
</I18nProvider>
)
}
it('en labels exist for form field add buttons', () => {
renderWith('en')
expect(screen.getByRole('button', { name: 'Add Text' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Email' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Tel' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Date' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Textarea' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Checkbox' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Select' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Radio' })).toBeInTheDocument()
})
it('ko labels exist for form field add buttons', () => {
renderWith('ko')
expect(screen.getByRole('button', { name: '텍스트 필드 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '이메일 필드 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '전화번호 필드 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '날짜 필드 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '텍스트영역 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '체크박스 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '셀렉트 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '라디오 추가' })).toBeInTheDocument()
})
})
+32
View File
@@ -0,0 +1,32 @@
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import React from 'react'
import BuilderClientPage from '@/components/BuilderClientPage'
import I18nProvider from '@/components/I18nProvider'
import en from '@/messages/en.json'
import ko from '@/messages/ko.json'
describe('i18n regression - misc labels (title/preview/sections)', () => {
function renderWith(locale: 'en' | 'ko') {
const messages = locale === 'en' ? en : ko
return render(
<I18nProvider locale={locale} messages={messages as unknown as Record<string, string>}>
<BuilderClientPage />
</I18nProvider>
)
}
it('en: builder title, preview text, sections heading present', () => {
renderWith('en')
expect(screen.getByRole('heading', { name: 'Landing Page Builder' })).toBeInTheDocument()
expect(screen.getByText('Preview (static)')).toBeInTheDocument()
expect(screen.getByText('Sections')).toBeInTheDocument()
})
it('ko: builder title, preview text, sections heading present', () => {
renderWith('ko')
expect(screen.getByRole('heading', { name: '랜딩 페이지 빌더' })).toBeInTheDocument()
expect(screen.getByText('미리보기(정적)')).toBeInTheDocument()
expect(screen.getByText('섹션')).toBeInTheDocument()
})
})
+62
View File
@@ -0,0 +1,62 @@
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import React from 'react'
import BuilderClientPage from '@/components/BuilderClientPage'
import I18nProvider from '@/components/I18nProvider'
import en from '@/messages/en.json'
import ko from '@/messages/ko.json'
function renderWith(locale: 'en' | 'ko') {
const messages = locale === 'en' ? en : ko
return render(
<I18nProvider locale={locale} messages={messages as unknown as Record<string, string>}>
<BuilderClientPage />
</I18nProvider>
)
}
describe('i18n regression - key presence on main UI', () => {
it('en: shows expected labels and buttons', () => {
renderWith('en')
expect(screen.getByRole('heading', { name: 'Landing Page Builder' })).toBeInTheDocument()
expect(screen.getByLabelText('Title')).toBeInTheDocument()
expect(screen.getByLabelText('Locale')).toBeInTheDocument()
expect(screen.getByLabelText('Primary Color')).toBeInTheDocument()
expect(screen.getByLabelText('Font Family')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Export ZIP' })).toBeInTheDocument()
// form actions
expect(screen.getByRole('button', { name: 'Add Text' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Email' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Tel' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Date' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Textarea' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Checkbox' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Select' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add Radio' })).toBeInTheDocument()
// preview
expect(screen.getByRole('button', { name: 'Desktop' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Mobile' })).toBeInTheDocument()
})
it('ko: shows expected labels and buttons', () => {
renderWith('ko')
expect(screen.getByRole('heading', { name: '랜딩 페이지 빌더' })).toBeInTheDocument()
expect(screen.getByLabelText('제목')).toBeInTheDocument()
expect(screen.getByLabelText('로케일')).toBeInTheDocument()
expect(screen.getByLabelText('대표 색상')).toBeInTheDocument()
expect(screen.getByLabelText('폰트')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'ZIP 내보내기' })).toBeInTheDocument()
// form actions
expect(screen.getByRole('button', { name: '텍스트 필드 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '이메일 필드 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '전화번호 필드 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '날짜 필드 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '텍스트영역 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '체크박스 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '셀렉트 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '라디오 추가' })).toBeInTheDocument()
// preview
expect(screen.getByRole('button', { name: 'Desktop' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Mobile' })).toBeInTheDocument()
})
})
+32
View File
@@ -0,0 +1,32 @@
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import React from 'react'
import BuilderClientPage from '@/components/BuilderClientPage'
import I18nProvider from '@/components/I18nProvider'
import en from '@/messages/en.json'
import ko from '@/messages/ko.json'
describe('i18n regression - sidebar add actions (en/ko)', () => {
function renderWith(locale: 'en' | 'ko') {
const messages = locale === 'en' ? en : ko
return render(
<I18nProvider locale={locale} messages={messages as unknown as Record<string, string>}>
<BuilderClientPage />
</I18nProvider>
)
}
it('en labels exist', () => {
renderWith('en')
expect(screen.getByRole('button', { name: 'Add Hero' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add FAQ' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add CTA' })).toBeInTheDocument()
})
it('ko labels exist', () => {
renderWith('ko')
expect(screen.getByRole('button', { name: '히어로 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'FAQ 추가' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'CTA 추가' })).toBeInTheDocument()
})
})
@@ -0,0 +1,31 @@
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import React from 'react'
import BuilderClientPage from '@/components/BuilderClientPage'
import I18nProvider from '@/components/I18nProvider'
import en from '@/messages/en.json'
describe('Preview viewport toggle ARIA regression', () => {
it('Desktop/Mobile buttons expose stable ARIA attributes', () => {
render(
<I18nProvider locale="en" messages={en as unknown as Record<string, string>}>
<BuilderClientPage />
</I18nProvider>
)
const desktop = screen.getByRole('button', { name: 'Desktop' })
const mobile = screen.getByRole('button', { name: 'Mobile' })
// describedby shared help
expect(desktop).toHaveAttribute('aria-describedby', 'preview-toggle-help')
expect(mobile).toHaveAttribute('aria-describedby', 'preview-toggle-help')
// controls preview container
expect(desktop).toHaveAttribute('aria-controls', 'preview-container')
expect(mobile).toHaveAttribute('aria-controls', 'preview-container')
// default pressed state: Desktop true, Mobile false
expect(desktop).toHaveAttribute('aria-pressed', 'true')
expect(mobile).toHaveAttribute('aria-pressed', 'false')
})
})
+8
View File
@@ -0,0 +1,8 @@
// Inspector a11y helper: verify input ↔ helptext wiring via aria-describedby
import { expect } from 'vitest'
export function assertDescribedBy(input: HTMLElement, helpId: string) {
expect(input).toHaveAttribute('aria-describedby', helpId)
const help = (input.ownerDocument || document).getElementById(helpId)
expect(help).not.toBeNull()
}