tests/{unit,integration,e2e}/, at the repo root (not under frontend/,
since e2e exercises the frontend and PocketBase together) — all
against real code rather than mocks:
- unit/: Vitest (nuxt environment) — pure functions in server/utils
and shared/utils, plus components/composables via
@nuxt/test-utils/runtime's mountSuspended.
- integration/: Vitest (node environment) — @nuxt/test-utils/e2e's
setup() builds and runs the real Nitro server against a real
ephemeral PocketBase instance, exercising the actual /content/* and
/admin routes end to end.
- e2e/: Playwright, a real browser against the built app and another
ephemeral PocketBase, covering the user-facing flows (home, blog
archive, article detail incl. JSON-LD, category filter, 404s, the
/admin redirect).
The Vitest/Playwright tooling (and node_modules) stays in frontend/,
the repo's only npm project; its configs just point at ../tests/.
Playwright resolves packages from the node_modules nearest each spec
file, so `npm run test:e2e` first symlinks tests/node_modules to
frontend/node_modules (pretest:e2e, idempotent, gitignored).
tests/support/pocketbase.ts is the shared piece behind integration
and e2e: it downloads the same pinned PocketBase binary
pocketbase/Dockerfile uses, starts it against the real
pocketbase/pb_migrations/ (not a copy), and seeds it from
tests/support/fixtures.ts — so these tests run against the exact
schema and API rules that ship to production, catching the class of
bug that only shows up when PocketBase actually enforces them (e.g. a
wrong filter/fields query string silently returning nothing, or too
much).
CLAUDE.md and docs/frontend.md document the new npm scripts and the
single-test commands for each layer.
67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { renderMarkdown, summarise, toMediaImage } from '../../frontend/server/utils/pocketbase'
|
|
|
|
describe('renderMarkdown', () => {
|
|
it('returns an empty string for null/undefined/empty input', () => {
|
|
expect(renderMarkdown(null)).toBe('')
|
|
expect(renderMarkdown(undefined)).toBe('')
|
|
expect(renderMarkdown('')).toBe('')
|
|
})
|
|
|
|
it('converts Markdown to HTML', () => {
|
|
const html = renderMarkdown('# Titolo\n\nCorpo **grassetto**.')
|
|
expect(html).toContain('<h1>Titolo</h1>')
|
|
expect(html).toContain('<strong>grassetto</strong>')
|
|
})
|
|
})
|
|
|
|
describe('summarise', () => {
|
|
it('returns short text unchanged, stripped of Markdown syntax', () => {
|
|
expect(summarise('# Titolo\n\nTesto breve.')).toBe('Titolo Testo breve.')
|
|
})
|
|
|
|
it('strips code fences, images and unwraps links', () => {
|
|
const source = '```js\nconst x = 1\n```\n [testo](https://example.com) fine.'
|
|
expect(summarise(source)).toBe('testo fine.')
|
|
})
|
|
|
|
it('clips long text on a word boundary with an ellipsis', () => {
|
|
const source = 'parola '.repeat(40).trim()
|
|
const result = summarise(source, 20)
|
|
expect(result.length).toBeLessThanOrEqual(21)
|
|
expect(result.endsWith('…')).toBe(true)
|
|
expect(result).not.toContain(' …')
|
|
})
|
|
|
|
it('returns an empty string for null/undefined input', () => {
|
|
expect(summarise(null)).toBe('')
|
|
expect(summarise(undefined)).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('toMediaImage', () => {
|
|
it('returns null when there is no filename', () => {
|
|
expect(toMediaImage('articles', 'abc123', '', 'alt')).toBeNull()
|
|
expect(toMediaImage('articles', 'abc123', null, 'alt')).toBeNull()
|
|
expect(toMediaImage('articles', 'abc123', undefined, 'alt')).toBeNull()
|
|
})
|
|
|
|
it('builds the PocketBase file URL from collection, id and filename', () => {
|
|
expect(toMediaImage('articles', 'abc123', 'cover.jpg', 'Testo alternativo')).toEqual({
|
|
url: '/api/files/articles/abc123/cover.jpg',
|
|
alt: 'Testo alternativo',
|
|
})
|
|
})
|
|
|
|
it('normalises a missing/empty alt to null', () => {
|
|
expect(toMediaImage('articles', 'abc123', 'cover.jpg', null)).toEqual({
|
|
url: '/api/files/articles/abc123/cover.jpg',
|
|
alt: null,
|
|
})
|
|
expect(toMediaImage('articles', 'abc123', 'cover.jpg', '')).toEqual({
|
|
url: '/api/files/articles/abc123/cover.jpg',
|
|
alt: null,
|
|
})
|
|
})
|
|
})
|