Shumiland/tests/unit/hooks/revalidatePath.test.ts
Vadym Samoilenko cca4ea1d55
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: implement full frontend — all sections, components, Figma Code Connect
- All 8 home page sections: Hero, Locations slider, WhyParents accordion,
  Birthday pricing cards, Video, Gallery, Reviews slider, News
- UI components: NavLink, BtnPrimary, BtnGradient, BtnDetails, AccordionItem
- Layout: sticky Header (NavLink + BtnPrimary), Footer with logo
- Figma Code Connect: 5 components published (.figma.tsx + figma.config.json)
- Public assets: all Figma images and SVGs exported
- Pages: /kvytky, /lokatsii, /blog, /dni-narodzhennia, /grupovi-vidviduvannia
- Tests: Vitest unit/api suites, Playwright e2e screenshots
- Payload CMS: blocks, collections, seed data updates
- Hero negative-margin to extend behind sticky header
- Custom Tailwind breakpoints: lg=1440px, xl=1920px
- Fix ESLint config: drop FlatCompat, use eslint-config-next flat export
- Add tsconfig.tsbuildinfo, test-results/, agentdb.rvf* to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 16:40:56 +01:00

183 lines
5.4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
const mockFetch = vi.fn()
vi.stubGlobal('fetch', mockFetch)
let revalidateAfterChange: (args: {
doc: Record<string, unknown>
collection: { slug: string }
req: object
previousDoc: object
context: object
operation: string
}) => unknown
let revalidateGlobalAfterChange: (args: {
doc: object
global: { slug: string }
req: object
context: object
previousDoc: object
}) => unknown
beforeEach(async () => {
vi.resetModules()
mockFetch.mockReset()
vi.stubEnv('NEXT_PUBLIC_SITE_URL', 'http://localhost:3000')
vi.stubEnv('REVALIDATE_SECRET', 'test-revalidate-secret')
const mod = await import('@/hooks/revalidatePath')
revalidateAfterChange = mod.revalidateAfterChange as typeof revalidateAfterChange
revalidateGlobalAfterChange = mod.revalidateGlobalAfterChange as typeof revalidateGlobalAfterChange
})
afterEach(() => {
vi.restoreAllMocks()
})
describe('revalidateAfterChange', () => {
it('returns the doc unchanged (fire-and-forget pattern)', () => {
mockFetch.mockResolvedValueOnce({ ok: true, status: 200 })
const doc = { id: '1', slug: 'about-us' }
const result = revalidateAfterChange({
doc,
collection: { slug: 'pages' },
req: {},
previousDoc: {},
context: {},
operation: 'update',
})
expect(result).toBe(doc)
})
it('POSTs to /api/revalidate with slug and collection when doc has slug', async () => {
mockFetch.mockResolvedValueOnce({ ok: true })
revalidateAfterChange({
doc: { id: '1', slug: 'about-us' },
collection: { slug: 'pages' },
req: {},
previousDoc: {},
context: {},
operation: 'update',
})
await new Promise((r) => setTimeout(r, 0))
expect(mockFetch).toHaveBeenCalledOnce()
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit]
expect(url).toBe('http://localhost:3000/api/revalidate')
expect((init.headers as Record<string, string>)['Authorization']).toBe(
'Bearer test-revalidate-secret'
)
const body = JSON.parse(init.body as string)
expect(body).toMatchObject({ slug: 'about-us', collection: 'pages' })
})
it('sends slug as undefined when doc has no slug field', async () => {
mockFetch.mockResolvedValueOnce({ ok: true })
revalidateAfterChange({
doc: { id: '2' },
collection: { slug: 'media' },
req: {},
previousDoc: {},
context: {},
operation: 'create',
})
await new Promise((r) => setTimeout(r, 0))
const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]
const body = JSON.parse(init.body as string)
expect(body.collection).toBe('media')
// doc.slug is undefined, coerced to undefined in JSON → omitted or undefined
expect(body.slug).toBeUndefined()
})
it('does not throw when fetch rejects (errors are swallowed)', () => {
mockFetch.mockRejectedValueOnce(new Error('network error'))
expect(() =>
revalidateAfterChange({
doc: { id: '1', slug: 'test' },
collection: { slug: 'pages' },
req: {},
previousDoc: {},
context: {},
operation: 'update',
})
).not.toThrow()
})
it('does not throw when revalidate endpoint returns non-ok (errors are swallowed)', async () => {
mockFetch.mockResolvedValueOnce({ ok: false, status: 401 })
expect(() =>
revalidateAfterChange({
doc: { id: '1', slug: 'test' },
collection: { slug: 'pages' },
req: {},
previousDoc: {},
context: {},
operation: 'update',
})
).not.toThrow()
})
it('uses NEXT_PUBLIC_SITE_URL env for the request URL', async () => {
vi.stubEnv('NEXT_PUBLIC_SITE_URL', 'https://shumiland.ua')
vi.resetModules()
const mod = await import('@/hooks/revalidatePath')
mockFetch.mockResolvedValueOnce({ ok: true })
;(
mod.revalidateAfterChange as typeof revalidateAfterChange
)({
doc: { id: '1', slug: 'home' },
collection: { slug: 'pages' },
req: {},
previousDoc: {},
context: {},
operation: 'update',
})
await new Promise((r) => setTimeout(r, 0))
expect(mockFetch.mock.calls[0]?.[0]).toBe('https://shumiland.ua/api/revalidate')
})
})
describe('revalidateGlobalAfterChange', () => {
it('returns the doc unchanged', () => {
mockFetch.mockResolvedValueOnce({ ok: true })
const doc = { id: 'g1' }
const result = revalidateGlobalAfterChange({
doc,
global: { slug: 'header' },
req: {},
context: {},
previousDoc: {},
})
expect(result).toBe(doc)
})
it('POSTs to /api/revalidate with global slug', async () => {
mockFetch.mockResolvedValueOnce({ ok: true })
revalidateGlobalAfterChange({
doc: {},
global: { slug: 'header' },
req: {},
context: {},
previousDoc: {},
})
await new Promise((r) => setTimeout(r, 0))
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit]
expect(url).toBe('http://localhost:3000/api/revalidate')
const body = JSON.parse(init.body as string)
expect(body).toEqual({ global: 'header' })
})
it('does not throw when fetch rejects', () => {
mockFetch.mockRejectedValueOnce(new Error('timeout'))
expect(() =>
revalidateGlobalAfterChange({
doc: {},
global: { slug: 'footer' },
req: {},
context: {},
previousDoc: {},
})
).not.toThrow()
})
})