Files
RenewCMS/web/src/components/__tests__/SeoLanguageCards.spec.ts
T

138 lines
4.6 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { initializeLocale } from '@/i18n'
import type { LocalizedSeoContext } from '@/lib/localizedSeo'
import type { ResolvedSeo } from '@/lib/seoTypes'
import router from '@/router'
import SeoLanguageCards from '../SeoLanguageCards.vue'
import { byText, mountInApp, text, unmountApp } from './directoryKit'
const PATTERN = 'https://journal.example.com/{lang}/{slug}'
const CANONICAL = 'https://journal.example.com/fr/comment-nous-chiffrons-une-renovation'
const ALTERNATES = [
{
id: 10,
lang: 'en',
story_id: 5,
slug: 'how-we-price-a-bathroom-renovation',
url: 'https://journal.example.com/en/how-we-price-a-bathroom-renovation',
},
{ id: 11, lang: 'de', story_id: 5, slug: 'so-kalkulieren-wir-eine-badsanierung', url: null },
]
function resolved(locale = 'fr_FR'): ResolvedSeo {
return {
title: 'Couts de renovation | Journal',
description: '',
canonical_url: CANONICAL,
robots: 'index, follow',
og: {
type: 'article',
title: '',
description: '',
image: '',
image_alt: '',
url: '',
site_name: 'Journal',
locale,
},
twitter: { card: 'summary', title: '', description: '', image: '', image_alt: '' },
image: { url: '', alt: '', source: 'none' },
authors: [],
published_at: null,
modified_at: '2026-09-26T11:02:00Z',
fallbacks: { title: 'title', description: 'none', image: 'none', canonical: 'pattern' },
json_ld: {},
alternates: ALTERNATES,
} as ResolvedSeo
}
function context(overrides: Partial<LocalizedSeoContext> = {}): LocalizedSeoContext {
return {
lang: 'fr',
siteName: 'Journal',
siteKey: 'journal',
pattern: PATTERN,
defaultLang: 'en',
disabledLangs: ['es'],
published: true,
socialState: 'derived',
resolved: resolved(),
...overrides,
}
}
function card(id: string) {
return document.body.querySelector(`[data-testid="${id}"]`)
}
beforeEach(() => {
localStorage.clear()
initializeLocale()
})
afterEach(() => {
unmountApp()
vi.restoreAllMocks()
})
describe('Search and sharing cards for one language', () => {
it('shows the canonical with its source note', async () => {
await mountInApp(SeoLanguageCards, { context: context() })
const canonical = text(card('seo-canonical-card'))
expect(canonical).toContain('Canonical')
expect(canonical).toContain(CANONICAL)
expect(canonical).toContain(`Built from the Public URL pattern ${PATTERN}.`)
})
it('lists the published alternates with their URL, or their slug without one, and the disabled note', async () => {
await mountInApp(SeoLanguageCards, { context: context() })
const rows = [...document.body.querySelectorAll('[data-testid="seo-alternate-row"]')]
expect(rows.map((row) => text(row))).toEqual([
'en https://journal.example.com/en/how-we-price-a-bathroom-renovation Published',
'de so-kalkulieren-wir-eine-badsanierung Published',
])
expect(rows[0]!.querySelector('[aria-label]')!.getAttribute('aria-label')).toBe('English')
const alternates = text(card('seo-alternates-card'))
expect(alternates).toContain('Alternate languages')
expect(alternates).toContain('Published versions in enabled languages only.')
expect(alternates).toContain('Drafts and the disabled Spanish language are left out.')
})
it('shows the derived social locale', async () => {
await mountInApp(SeoLanguageCards, { context: context() })
const social = text(card('seo-social-card'))
expect(social).toContain('Social locale')
expect(social).toContain('og:locale')
expect(social).toContain('Derived: fr_FR')
})
it('shows Social locale not set when nothing resolves', async () => {
await mountInApp(SeoLanguageCards, { context: context({ resolved: resolved('') }) })
expect(text(card('seo-social-card'))).toContain('Social locale not set')
})
it('replaces the canonical and alternates with Public URL not configured and opens its settings', async () => {
const push = vi.spyOn(router, 'push').mockResolvedValue(undefined)
await mountInApp(SeoLanguageCards, { context: context({ pattern: null }) })
expect(card('seo-canonical-card')).toBeNull()
expect(card('seo-alternates-card')).toBeNull()
expect(text(document.body)).toContain('Public URL not configured')
expect(text(document.body)).toContain(
'Add a public URL pattern for Journal to show page and canonical addresses. API links by article ID still work.',
)
expect(card('seo-social-card')).not.toBeNull()
byText('button', 'Open Public URL settings')!.click()
await flushPromises()
expect(push).toHaveBeenCalledWith('/settings/sites/journal/public-url')
})
})