Shumiland/tests/unit/hooks/slugify.test.ts
Vadym Samoilenko 9b41fa447a
Some checks are pending
CI / Type Check (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Unit Tests (push) Waiting to run
Deploy / Build & Push Image (push) Waiting to run
Deploy / Deploy to VPS (push) Blocked by required conditions
feat: complete backend B1-B7 — Payload CMS, ezy payments, leads, deploy
- B1: Next.js 15 + Payload CMS 3.0 + Postgres 16, ESLint, Prettier, Husky, Vitest
- B2: 9 collections, 6 globals, 12 Page Builder blocks, access control, slugify/revalidate hooks
- B3: ezy.com.ua payments, Binotel HMAC webhook, leads API, Telegram bot, Resend email, rate limiting
- B4: Tariffs collection with ezy API sync (cron + manual), dynamic pricing source-of-truth
- B5: 13 test files covering unit libs and all API routes
- B6: Dockerfile multi-stage, docker-compose.prod.yml, nginx.conf SSL, GitHub Actions CI/CD, health endpoint
- B7: docs/admin-guide-ua.md (marketer guide), docs/deploy.md (VPS instructions), README quickstart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 19:14:54 +01:00

109 lines
4.2 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { slugifyBeforeChange } from '@/hooks/slugify'
// Minimal args satisfying CollectionBeforeChangeHook signature
function makeArgs(
data: Record<string, unknown>,
operation: 'create' | 'update'
): Parameters<typeof slugifyBeforeChange>[0] {
return {
data,
operation,
req: {} as never,
originalDoc: undefined,
context: {},
collection: {} as never,
}
}
describe('slugifyBeforeChange', () => {
describe('create operation', () => {
it('generates a latin slug from a Cyrillic title', () => {
const result = slugifyBeforeChange(makeArgs({ title: 'Шумиленд' }, 'create'))
expect((result as { slug: string }).slug).toBe('shumilend')
})
it('generates a slug for a multi-word Cyrillic title', () => {
const result = slugifyBeforeChange(makeArgs({ title: 'Диво Ліс' }, 'create'))
expect((result as { slug: string }).slug).toBe('divo-ls')
})
it('generates a slug for a Ukrainian common noun', () => {
const result = slugifyBeforeChange(makeArgs({ title: 'Новини' }, 'create'))
expect((result as { slug: string }).slug).toBe('novini')
})
it('produces only lowercase alphanumeric and hyphen characters', () => {
const result = slugifyBeforeChange(makeArgs({ title: 'Шумиленд' }, 'create'))
const slug = (result as { slug: string }).slug
expect(slug).toMatch(/^[a-z0-9-]+$/)
})
it('overwrites any slug value supplied on create', () => {
// On create, slug is always regenerated from title
const result = slugifyBeforeChange(makeArgs({ title: 'Новини', slug: 'ignore-me' }, 'create'))
expect((result as { slug: string }).slug).toBe('novini')
})
it('returns data unchanged when title is absent on create', () => {
const data = { status: 'draft' }
const result = slugifyBeforeChange(makeArgs(data, 'create'))
expect(result).toEqual(data)
})
it('returns data unchanged when title is not a string', () => {
const data = { title: 42 }
const result = slugifyBeforeChange(makeArgs(data, 'create'))
expect(result).toEqual(data)
expect((result as Record<string, unknown>)['slug']).toBeUndefined()
})
})
describe('update operation', () => {
it('does not overwrite a non-empty existing slug on update', () => {
const result = slugifyBeforeChange(
makeArgs({ title: 'New Title', slug: 'existing-slug' }, 'update')
)
expect((result as { slug: string }).slug).toBe('existing-slug')
})
it('generates slug from title when slug is empty string on update', () => {
const result = slugifyBeforeChange(makeArgs({ title: 'Новини', slug: '' }, 'update'))
expect((result as { slug: string }).slug).toBe('novini')
})
it('generates slug from title when slug is undefined on update', () => {
const result = slugifyBeforeChange(makeArgs({ title: 'Шумиленд' }, 'update'))
expect((result as { slug: string }).slug).toBe('shumilend')
})
it('returns data unchanged when slug is missing and no title on update', () => {
const data = { status: 'published' }
const result = slugifyBeforeChange(makeArgs(data, 'update'))
expect(result).toEqual(data)
})
})
describe('slug format', () => {
it('collapses multiple consecutive hyphens', () => {
// A title that produces consecutive hyphens after stripping special chars
const result = slugifyBeforeChange(makeArgs({ title: 'A B' }, 'create'))
const slug = (result as { slug: string }).slug
expect(slug).not.toMatch(/--/)
})
it('does not start or end with a hyphen', () => {
const result = slugifyBeforeChange(makeArgs({ title: 'Шумиленд' }, 'create'))
const slug = (result as { slug: string }).slug
expect(slug).not.toMatch(/^-/)
expect(slug).not.toMatch(/-$/)
})
it('produces a non-empty slug for any non-empty Cyrillic title', () => {
const result = slugifyBeforeChange(makeArgs({ title: 'Я' }, 'create'))
const slug = (result as { slug: string }).slug
expect(typeof slug).toBe('string')
expect(slug.length).toBeGreaterThan(0)
})
})
})