138 lines
4.6 KiB
TypeScript
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')
|
|
})
|
|
})
|