mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:53:05 +00:00
516 lines
22 KiB
TypeScript
516 lines
22 KiB
TypeScript
import { randomUUID } from 'node:crypto'
|
||
|
||
import type { APIRequestContext, Page } from '@playwright/test'
|
||
|
||
import { adminHeaders } from '../helpers/adminApi'
|
||
import { seedTestUser } from '../helpers/seedUser'
|
||
import { expect, test } from './fixtures/e2eTest'
|
||
|
||
/**
|
||
* S27 — the public Central de Conteúdos over real HTTP: the published pieces
|
||
* of C211 list with the five facets, the media is only fetched on the play,
|
||
* the share sheet carries the editable vote message with no recipient, the
|
||
* download name is legible and the kill switch flips the page, the discovery
|
||
* link and the piece page without a deploy.
|
||
*
|
||
* Pieces and media are seeded through the deployed REST API (admin session) so
|
||
* the server process runs the real cache hooks; a Local API call from the
|
||
* runner would throw on `revalidateTag` (same reason as the S21 spec).
|
||
*
|
||
* The tests are serial: the empty-state assertions need a global window with
|
||
* zero published pieces, and only this spec owns content piece rows.
|
||
*/
|
||
|
||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:3000'
|
||
|
||
// 1×1 opaque PNG — enough for the photo upload.
|
||
const TEST_PNG = Buffer.from(
|
||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
|
||
'base64',
|
||
)
|
||
const TEST_MP4 = Buffer.from('fixture-content-piece-video-bytes')
|
||
|
||
type Headers = Record<string, string>
|
||
|
||
const createdPieceIds: number[] = []
|
||
const createdMediaIds: number[] = []
|
||
|
||
const uniqueMarker = () => randomUUID().slice(0, 8)
|
||
|
||
const mediaFor = (type: 'video' | 'foto' | 'texto') => {
|
||
if (type === 'video') return { mimetype: 'video/mp4', buffer: TEST_MP4, extension: 'mp4' }
|
||
if (type === 'foto') return { mimetype: 'image/png', buffer: TEST_PNG, extension: 'png' }
|
||
return {
|
||
mimetype: 'text/plain',
|
||
buffer: Buffer.from('Peça voto pra Solla 1313.'),
|
||
extension: 'txt',
|
||
}
|
||
}
|
||
|
||
const createContentMedia = async (
|
||
request: APIRequestContext,
|
||
headers: Headers,
|
||
name: string,
|
||
mimetype: string,
|
||
buffer: Buffer,
|
||
): Promise<number> => {
|
||
const response = await request.post(`${BASE_URL}/api/contentMedia`, {
|
||
headers,
|
||
multipart: {
|
||
_payload: JSON.stringify({ alt: name }),
|
||
file: { name, mimeType: mimetype, buffer },
|
||
},
|
||
})
|
||
expect(response.ok(), await response.text()).toBeTruthy()
|
||
const id = ((await response.json()) as { doc: { id: number } }).doc.id
|
||
createdMediaIds.push(id)
|
||
return id
|
||
}
|
||
|
||
const createPiece = async (
|
||
request: APIRequestContext,
|
||
headers: Headers,
|
||
data: {
|
||
title: string
|
||
type: 'video' | 'foto' | 'texto' | 'audio' | 'card'
|
||
withMedia?: boolean
|
||
origin?: 'arquivo' | 'instagram' | 'youtube'
|
||
sourceUrl?: string
|
||
},
|
||
): Promise<{ id: number; slug: string }> => {
|
||
const mediaKind = data.type === 'video' || data.type === 'foto' ? data.type : 'texto'
|
||
const mediaSpec = mediaFor(mediaKind)
|
||
const media =
|
||
data.withMedia === false
|
||
? null
|
||
: await createContentMedia(
|
||
request,
|
||
headers,
|
||
`${data.title.replace(/\s+/g, '-')}.${mediaSpec.extension}`,
|
||
mediaSpec.mimetype,
|
||
mediaSpec.buffer,
|
||
)
|
||
|
||
const response = await request.post(`${BASE_URL}/api/contentPiece`, {
|
||
headers,
|
||
data: {
|
||
title: data.title,
|
||
type: data.type,
|
||
status: 'publicado',
|
||
origin: data.origin ?? 'arquivo',
|
||
...(media ? { media } : {}),
|
||
...(data.sourceUrl ? { sourceUrl: data.sourceUrl } : {}),
|
||
},
|
||
})
|
||
expect(response.ok(), await response.text()).toBeTruthy()
|
||
const doc = ((await response.json()) as { doc: { id: number; slug: string } }).doc
|
||
createdPieceIds.push(doc.id)
|
||
return { id: doc.id, slug: doc.slug }
|
||
}
|
||
|
||
const setPieceStatus = async (
|
||
request: APIRequestContext,
|
||
headers: Headers,
|
||
id: number,
|
||
status: 'rascunho' | 'publicado',
|
||
): Promise<void> => {
|
||
const response = await request.patch(`${BASE_URL}/api/contentPiece/${id}`, {
|
||
headers,
|
||
data: { status },
|
||
})
|
||
expect(response.ok(), await response.text()).toBeTruthy()
|
||
}
|
||
|
||
/** Zero published pieces globally so the empty-state assertions stay deterministic. */
|
||
const unpublishEveryPiece = async (request: APIRequestContext, headers: Headers) => {
|
||
const listed = await request.get(`${BASE_URL}/api/contentPiece?limit=0&depth=0`, { headers })
|
||
expect(listed.ok(), await listed.text()).toBeTruthy()
|
||
const { docs } = (await listed.json()) as { docs: { id: number; status?: string | null }[] }
|
||
for (const doc of docs) {
|
||
if (doc.status === 'publicado') await setPieceStatus(request, headers, doc.id, 'rascunho')
|
||
}
|
||
}
|
||
|
||
const mediaRequests = (page: Page) => {
|
||
const requests: string[] = []
|
||
page.on('request', (request) => {
|
||
if (/\/conteudos\/.+\/midia/.test(request.url())) requests.push(request.url())
|
||
})
|
||
return requests
|
||
}
|
||
|
||
/**
|
||
* C106/S28 — dynamic pages stream a transient hidden `S:` copy of the shell in
|
||
* the production build (the theme path reads `next/headers`, and any heavy
|
||
* render can defer behind `loading.tsx`). Wait for the stream to be collected
|
||
* before asserting with strict locators.
|
||
*/
|
||
const waitForSettledPage = async (page: Page) => {
|
||
await page.waitForFunction(() => document.querySelectorAll('div[id^="S:"]').length === 0)
|
||
}
|
||
|
||
test.describe.configure({ mode: 'serial' })
|
||
|
||
test.afterAll(async ({ request }) => {
|
||
const headers = await adminHeaders(request, BASE_URL).catch(() => null)
|
||
if (!headers) return
|
||
for (const id of createdPieceIds.splice(0)) {
|
||
// C213 — the anonymous events of the piece go with it (the spec owns both).
|
||
await request
|
||
.delete(`${BASE_URL}/api/contentEvent?where[subjectId][equals]=${id}`, { headers })
|
||
.catch(() => undefined)
|
||
await request.delete(`${BASE_URL}/api/contentPiece/${id}`, { headers }).catch(() => undefined)
|
||
}
|
||
for (const id of createdMediaIds.splice(0)) {
|
||
await request.delete(`${BASE_URL}/api/contentMedia/${id}`, { headers }).catch(() => undefined)
|
||
}
|
||
})
|
||
|
||
test.describe('Frontend Central de Conteúdos (S27)', () => {
|
||
test.beforeAll(async ({ request }) => {
|
||
await seedTestUser()
|
||
const headers = await adminHeaders(request, BASE_URL)
|
||
await unpublishEveryPiece(request, headers)
|
||
})
|
||
|
||
test('lists the published pieces, keeps the media lazy and shares the vote', async ({
|
||
page,
|
||
request,
|
||
}) => {
|
||
const headers = await adminHeaders(request, BASE_URL)
|
||
const videoTitle = `Fim da escala ${uniqueMarker()}`
|
||
const photoTitle = `Solla no SUS ${uniqueMarker()}`
|
||
const video = await createPiece(request, headers, { title: videoTitle, type: 'video' })
|
||
await createPiece(request, headers, { title: photoTitle, type: 'foto' })
|
||
|
||
const media = mediaRequests(page)
|
||
|
||
const response = await page.goto('/conteudos')
|
||
expect(response?.status()).toBe(200)
|
||
await waitForSettledPage(page)
|
||
await expect(
|
||
page.getByRole('heading', { name: 'Uma mensagem sua pode conquistar mais um voto.' }),
|
||
).toBeVisible()
|
||
await expect(page.getByText('Central de Conteúdos').first()).toBeVisible()
|
||
await expect(page.locator('article[data-content-piece]')).toHaveCount(2)
|
||
await expect(page.locator('article[data-content-piece-invite]')).toHaveCount(1)
|
||
|
||
// The video mounts no media element and fetches nothing before the play
|
||
// (the photo preview is the only media the catalogue loads on its own).
|
||
await expect(page.locator('video')).toHaveCount(0)
|
||
await expect(page.locator('audio')).toHaveCount(0)
|
||
expect(media.filter((url) => url.includes(video.slug))).toHaveLength(0)
|
||
|
||
// Play mounts only the tapped piece and fetches only its media.
|
||
await page.getByRole('button', { name: `Reproduzir ${videoTitle}` }).click()
|
||
await expect(page.locator('video')).toHaveCount(1)
|
||
await expect.poll(() => media.filter((url) => url.includes(video.slug)).length).toBe(1)
|
||
|
||
// The download carries the legible name built from the slug.
|
||
await expect(page.getByRole('link', { name: `Baixar ${videoTitle}` })).toHaveAttribute(
|
||
'download',
|
||
`jorge-solla-1313-${video.slug}.mp4`,
|
||
)
|
||
|
||
// The share sheet: editable vote message, wa.me without recipient.
|
||
await page.getByRole('button', { name: `Compartilhar ${videoTitle}` }).click()
|
||
const sheet = page.getByRole('dialog', { name: 'Compartilhar peça' })
|
||
await expect(sheet).toBeVisible()
|
||
const message = sheet.getByLabel('Mensagem para compartilhar')
|
||
await expect(message).toHaveValue(new RegExp(videoTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
|
||
const whatsApp = sheet.getByRole('link', { name: 'Abrir no WhatsApp' })
|
||
const href = await whatsApp.getAttribute('href')
|
||
expect(href).toContain('https://wa.me/?text=')
|
||
expect(new URL(href!).pathname).toBe('/')
|
||
expect(decodeURIComponent(href!)).toContain(`/conteudos/${video.slug}`)
|
||
await message.fill('Mensagem revisada pela pessoa.')
|
||
await expect(whatsApp).toHaveAttribute('href', /Mensagem\+revisada/)
|
||
|
||
// "A mídia" falls back to the download of the same file where the device
|
||
// cannot share files (headless Linux has no Web Share API).
|
||
const [downloadEvent] = await Promise.all([
|
||
page.waitForEvent('download'),
|
||
sheet.getByRole('button', { name: 'A mídia' }).click(),
|
||
])
|
||
expect(downloadEvent.suggestedFilename()).toBe(`jorge-solla-1313-${video.slug}.mp4`)
|
||
|
||
await sheet.getByRole('button', { name: 'Fechar', exact: true }).click()
|
||
await expect(sheet).toHaveCount(0)
|
||
})
|
||
|
||
test('filters by facet and term and shows the honest no-results state', async ({
|
||
page,
|
||
request,
|
||
}) => {
|
||
const headers = await adminHeaders(request, BASE_URL)
|
||
await unpublishEveryPiece(request, headers)
|
||
const marker = uniqueMarker()
|
||
const videoTitle = `Debate na rádio ${marker}`
|
||
const video = await createPiece(request, headers, { title: videoTitle, type: 'video' })
|
||
await createPiece(request, headers, { title: `Card do giro ${marker}`, type: 'foto' })
|
||
|
||
await page.goto(`/conteudos?tipo=video`)
|
||
await waitForSettledPage(page)
|
||
await expect(page.locator('article[data-content-piece]')).toHaveCount(1)
|
||
await expect(page.getByRole('link', { name: videoTitle, exact: true })).toBeVisible()
|
||
// A filtered board never shows the card invite.
|
||
await expect(page.locator('article[data-content-piece-invite]')).toHaveCount(0)
|
||
|
||
await page.goto(`/conteudos?q=${marker}`)
|
||
await expect(page.locator('article[data-content-piece]')).toHaveCount(2)
|
||
|
||
await page.goto(`/conteudos?tipo=video&q=${marker}`)
|
||
await waitForSettledPage(page)
|
||
await expect(page.locator('article[data-content-piece]')).toHaveCount(1)
|
||
await expect(page.getByRole('link', { name: /Remover filtro Tipo: Vídeo/ })).toBeVisible()
|
||
|
||
await page.goto('/conteudos?q=zzz-nao-existe')
|
||
await waitForSettledPage(page)
|
||
await expect(page.getByText('Busca: “zzz-nao-existe”')).toBeVisible()
|
||
const clear = page.getByRole('link', { name: 'Limpar filtros' })
|
||
await expect(clear).toHaveAttribute('href', '/conteudos')
|
||
await clear.click()
|
||
await expect(page).toHaveURL(/\/conteudos$/)
|
||
await expect(page.locator('article[data-content-piece]')).toHaveCount(2)
|
||
|
||
// The video card link opens the piece page with the back link.
|
||
await page.getByRole('link', { name: videoTitle, exact: true }).click()
|
||
await expect(page).toHaveURL(new RegExp(`/conteudos/${video.slug}$`))
|
||
await expect(page.getByRole('heading', { name: videoTitle })).toBeVisible()
|
||
await expect(page.getByRole('link', { name: 'Voltar à Central' })).toBeVisible()
|
||
})
|
||
|
||
test('offers the theme mode and degrades honestly without the provider key', async ({
|
||
page,
|
||
request,
|
||
}) => {
|
||
const headers = await adminHeaders(request, BASE_URL)
|
||
await unpublishEveryPiece(request, headers)
|
||
const marker = uniqueMarker()
|
||
const title = `Saúde do trabalhador ${marker}`
|
||
await createPiece(request, headers, { title, type: 'video' })
|
||
|
||
// The e2e build blanks DEEPSEEK_API_KEY (same as the C192 spec), so the
|
||
// theme mode always degrades: the exact results stay and the notice explains.
|
||
const response = await page.goto(`/conteudos?q=${marker}&mode=tema`)
|
||
expect(response?.status()).toBe(200)
|
||
await waitForSettledPage(page)
|
||
await expect(page.getByRole('heading', { name: `Resultados para “${marker}”` })).toBeVisible()
|
||
await expect(page.getByRole('radio', { name: 'Termo exato' })).toBeChecked()
|
||
await expect(page.getByRole('radio', { name: 'Por tema' })).toBeDisabled()
|
||
await expect(page.getByText('Busca por tema indisponível agora.')).toBeVisible()
|
||
await expect(page.getByRole('link', { name: title, exact: true })).toBeVisible()
|
||
await expect(page.getByText('Por que apareceu')).toHaveCount(0)
|
||
|
||
// A mode without a query has nothing to expand: no notice, exact is active.
|
||
await page.goto('/conteudos?mode=tema')
|
||
await waitForSettledPage(page)
|
||
await expect(page.getByText('Busca por tema indisponível agora.')).toHaveCount(0)
|
||
await expect(page.getByRole('radio', { name: 'Termo exato' })).toBeChecked()
|
||
await expect(page.getByRole('radio', { name: 'Por tema' })).toBeEnabled()
|
||
|
||
// Changing the mode submits the GET form with the query in force.
|
||
await page.goto(`/conteudos?q=${marker}`)
|
||
await waitForSettledPage(page)
|
||
await page.getByRole('radio', { name: 'Por tema' }).check()
|
||
await expect(page).toHaveURL(new RegExp(`q=${marker}&mode=tema$`))
|
||
// The URL commits before the new document renders: wait for the page's own
|
||
// content and for the streamed copy to be collected before the strict locator.
|
||
await expect(page.getByRole('heading', { name: `Resultados para “${marker}”` })).toHaveCount(1)
|
||
await waitForSettledPage(page)
|
||
await expect(page.getByText('Busca por tema indisponível agora.')).toBeVisible()
|
||
|
||
// Enter on the field searches without JS (the canonical block has no button).
|
||
await page.goto('/conteudos')
|
||
await waitForSettledPage(page)
|
||
const field = page.getByRole('searchbox', { name: 'Buscar peças' })
|
||
await field.fill(marker)
|
||
await field.press('Enter')
|
||
await expect(page).toHaveURL(new RegExp(`q=${marker}&mode=exato$`))
|
||
await expect(page.getByRole('link', { name: title, exact: true })).toBeVisible()
|
||
})
|
||
|
||
test('opens the piece page with OG preview and the same actions', async ({ page, request }) => {
|
||
const headers = await adminHeaders(request, BASE_URL)
|
||
await unpublishEveryPiece(request, headers)
|
||
const photoTitle = `Solla em defesa do SUS ${uniqueMarker()}`
|
||
const photo = await createPiece(request, headers, { title: photoTitle, type: 'foto' })
|
||
|
||
const response = await page.goto(`/conteudos/${photo.slug}`)
|
||
expect(response?.status()).toBe(200)
|
||
await expect(page.getByRole('heading', { name: photoTitle })).toBeVisible()
|
||
await expect(page.getByRole('link', { name: `Baixar ${photoTitle}` })).toHaveAttribute(
|
||
'download',
|
||
`jorge-solla-1313-${photo.slug}.png`,
|
||
)
|
||
await expect(page.locator('meta[property="og:title"]').first()).toHaveAttribute(
|
||
'content',
|
||
new RegExp(photoTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
|
||
)
|
||
await expect(page.locator('meta[property="og:image"]').first()).toHaveAttribute(
|
||
'content',
|
||
new RegExp(`/conteudos/${photo.slug}/midia$`),
|
||
)
|
||
|
||
await page.getByRole('button', { name: `Compartilhar ${photoTitle}` }).click()
|
||
const sheet = page.getByRole('dialog', { name: 'Compartilhar peça' })
|
||
await expect(sheet.getByLabel('Mensagem para compartilhar')).toHaveValue(
|
||
/Fiz\/achei esse material do Solla 1313/,
|
||
)
|
||
})
|
||
|
||
test('shares a link piece by the platform URL with no download action', async ({
|
||
page,
|
||
request,
|
||
}) => {
|
||
const headers = await adminHeaders(request, BASE_URL)
|
||
await unpublishEveryPiece(request, headers)
|
||
const linkTitle = `Post no Instagram ${uniqueMarker()}`
|
||
const shortcode = uniqueMarker().toUpperCase()
|
||
const link = await createPiece(request, headers, {
|
||
title: linkTitle,
|
||
type: 'video',
|
||
withMedia: false,
|
||
origin: 'instagram',
|
||
sourceUrl: `https://www.instagram.com/reel/${shortcode}/`,
|
||
})
|
||
|
||
await page.goto('/conteudos')
|
||
await waitForSettledPage(page)
|
||
await expect(page.locator('article[data-content-piece]')).toHaveCount(1)
|
||
await expect(page.getByRole('link', { name: `Baixar ${linkTitle}` })).toHaveCount(0)
|
||
|
||
await page.getByRole('button', { name: `Compartilhar ${linkTitle}` }).click()
|
||
const sheet = page.getByRole('dialog', { name: 'Compartilhar peça' })
|
||
await expect(sheet.getByRole('button', { name: 'A mídia' })).toHaveCount(0)
|
||
const href = await sheet.getByRole('link', { name: 'Abrir no WhatsApp' }).getAttribute('href')
|
||
expect(decodeURIComponent(href!)).toContain(`https://www.instagram.com/reel/${shortcode}/`)
|
||
|
||
await page.goto(`/conteudos/${link.slug}`)
|
||
await expect(page.getByText('A mídia permanece na plataforma original.')).toBeVisible()
|
||
await expect(
|
||
page.getByText('O compartilhamento usa o link da publicação no Instagram.'),
|
||
).toBeVisible()
|
||
})
|
||
|
||
test('counts the anonymous circulation of the piece', async ({ page, request, context }) => {
|
||
const headers = await adminHeaders(request, BASE_URL)
|
||
await unpublishEveryPiece(request, headers)
|
||
const title = `Circulação anônima ${uniqueMarker()}`
|
||
const piece = await createPiece(request, headers, { title, type: 'video' })
|
||
|
||
const eventCount = async (type: string): Promise<number> => {
|
||
const response = await request.get(
|
||
`${BASE_URL}/api/contentEvent?limit=0&depth=0&where[and][0][subjectId][equals]=${piece.id}&where[and][1][type][equals]=${type}`,
|
||
{ headers },
|
||
)
|
||
expect(response.ok(), await response.text()).toBeTruthy()
|
||
return ((await response.json()) as { totalDocs: number }).totalDocs
|
||
}
|
||
|
||
const beaconFor = (type: string) =>
|
||
page.waitForRequest((candidate) => {
|
||
if (!candidate.url().includes('/api/content-events')) return false
|
||
try {
|
||
return (candidate.postDataJSON() as { type?: string } | null)?.type === type
|
||
} catch {
|
||
return false
|
||
}
|
||
})
|
||
|
||
// Abertura: the piece page mount beacons once, carrying the public slug.
|
||
const openBeacon = beaconFor('abertura')
|
||
await page.goto(`/conteudos/${piece.slug}`)
|
||
expect((await openBeacon).postDataJSON()).toEqual({ type: 'abertura', pieceSlug: piece.slug })
|
||
await expect.poll(() => eventCount('abertura')).toBe(1)
|
||
|
||
// Download: the SERVER counts the full file request (the click alone would
|
||
// count a cancelled download).
|
||
const [download] = await Promise.all([
|
||
page.waitForEvent('download'),
|
||
page.getByRole('link', { name: `Baixar ${title}` }).click(),
|
||
])
|
||
expect(download.suggestedFilename()).toBe(`jorge-solla-1313-${piece.slug}.mp4`)
|
||
await expect.poll(() => eventCount('download')).toBe(1)
|
||
|
||
// WhatsApp and link stay apart, each counted on its own control.
|
||
await page.getByRole('button', { name: `Compartilhar ${title}` }).click()
|
||
const sheet = page.getByRole('dialog', { name: 'Compartilhar peça' })
|
||
await context.route('https://wa.me/**', (route) =>
|
||
route.fulfill({ body: '', contentType: 'text/html' }),
|
||
)
|
||
const whatsAppBeacon = beaconFor('compartilhar_whatsapp')
|
||
await sheet.getByRole('link', { name: 'Abrir no WhatsApp' }).click()
|
||
await whatsAppBeacon
|
||
|
||
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
|
||
const linkBeacon = beaconFor('compartilhar_link')
|
||
await sheet.getByRole('button', { name: 'Copiar link' }).click()
|
||
await linkBeacon
|
||
|
||
await expect.poll(() => eventCount('compartilhar_whatsapp')).toBe(1)
|
||
await expect.poll(() => eventCount('compartilhar_link')).toBe(1)
|
||
|
||
// The stored event is the subject only: no IP, cookie, user-agent or any
|
||
// other visitor identifier — the privacy contract, pinned row by row.
|
||
// (`variant` is the S32 state-deputy slug sub-key: a public catalog slug,
|
||
// never a visitor identifier.)
|
||
const listed = await request.get(
|
||
`${BASE_URL}/api/contentEvent?limit=1&depth=0&where[subjectId][equals]=${piece.id}`,
|
||
{ headers },
|
||
)
|
||
const doc = ((await listed.json()) as { docs: Record<string, unknown>[] }).docs[0]!
|
||
expect(Object.keys(doc).sort()).toEqual([
|
||
'createdAt',
|
||
'id',
|
||
'subjectId',
|
||
'subjectType',
|
||
'type',
|
||
'updatedAt',
|
||
'variant',
|
||
])
|
||
})
|
||
|
||
test('flips the kill switch, the discovery and the piece page without a deploy', async ({
|
||
page,
|
||
request,
|
||
}) => {
|
||
const headers = await adminHeaders(request, BASE_URL)
|
||
await unpublishEveryPiece(request, headers)
|
||
const title = `Peça do kill switch ${uniqueMarker()}`
|
||
const piece = await createPiece(request, headers, { title, type: 'video' })
|
||
|
||
await page.goto('/conteudos')
|
||
await waitForSettledPage(page)
|
||
await expect(page.getByRole('link', { name: 'Conteúdos' })).toBeVisible()
|
||
await expect(page.locator('article[data-content-piece]')).toHaveCount(1)
|
||
|
||
await setPieceStatus(request, headers, piece.id, 'rascunho')
|
||
|
||
await page.goto('/conteudos')
|
||
await waitForSettledPage(page)
|
||
await expect(
|
||
page.getByRole('heading', { name: 'As primeiras peças estão a caminho' }),
|
||
).toBeVisible()
|
||
await expect(page.locator('article[data-content-piece]')).toHaveCount(0)
|
||
await expect(page.getByRole('link', { name: 'Conteúdos' })).toHaveCount(0)
|
||
await expect
|
||
.poll(async () =>
|
||
(await (await request.get(`${BASE_URL}/conteudos`)).text()).includes('noindex, nofollow'),
|
||
)
|
||
.toBe(true)
|
||
|
||
// The raw HTTP status is the 404 contract and the honest copy is in the
|
||
// body (the browser is not navigated to the 404 URL: the failure guard
|
||
// fails a test on the expected resource error).
|
||
const unpublished = await request.get(`/conteudos/${piece.slug}`)
|
||
expect(unpublished.status()).toBe(404)
|
||
expect(await unpublished.text()).toContain('Esta peça não está disponível')
|
||
|
||
await setPieceStatus(request, headers, piece.id, 'publicado')
|
||
await page.goto('/conteudos')
|
||
await waitForSettledPage(page)
|
||
await expect(page.locator('article[data-content-piece]')).toHaveCount(1)
|
||
await expect(page.getByRole('link', { name: 'Conteúdos' })).toBeVisible()
|
||
})
|
||
})
|