import { es } from '@impuestos/i18n'; import { expect, test, type Page } from '@playwright/test'; import { findMonthWithoutDeclaration } from './db'; /** * Golden path 4 (SPEC.md section 13): review a declaration, approve it, take the PDF, and * walk the checklist that files it in Marangatu by hand. * * Serial and desktop only: approving is a one way door on shared demo data, and two * workers approving the same declaration is a race the product is right to reject. */ test.describe.configure({ mode: 'serial' }); async function signIn(page: Page): Promise { await page.goto('/es/login'); await page.getByLabel(es['auth.register.email']).fill('maria@demo.local'); await page.getByLabel(es['auth.login.password']).fill('demo-maria-1'); await page.getByRole('button', { name: es['auth.login.submit'] }).click(); await expect(page).toHaveURL(/\/es\/inicio$/); } test.describe('golden path 4: review, approve, download, file', () => { test.beforeEach(() => { test.skip(test.info().project.name !== 'desktop', 'approving mutates shared seed data'); }); test('takes a declaration all the way from generated to filed', async ({ page }) => { await signIn(page); // A month with documents and no declaration yet: approving cannot be undone, so the // test needs a period it has not already consumed. const period = findMonthWithoutDeclaration('maria@demo.local'); expect( period, 'every month with documents already has a declaration; run pnpm db:reset first', ).not.toBeNull(); await page.goto('/es/declaraciones'); await page.evaluate(async (target) => { const response = await fetch('/api/declarations/generate', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ formCode: '120', period: target }), }); if (!response.ok) throw new Error(`generate failed: ${response.status}`); }, period); await page.reload(); const ready = page .locator('main') .getByRole('link') .filter({ hasText: es['decl.status.ready'] }) .first(); await expect(ready).toBeVisible(); await ready.click(); // D2: the sentence comes before the form. await expect(page.getByRole('heading', { name: es['decl.summary.title'] })).toBeVisible(); await expect(page.getByText(/Vendiste Gs\. [\d.]+ y compraste Gs\. [\d.]+/)).toBeVisible(); // The official layout, in Spanish, marked as a draft until it is approved. await expect(page.getByRole('heading', { name: es['decl.preview.title'] })).toBeVisible(); await expect(page.getByText(es['decl.preview.spanishNote'])).toBeVisible(); // The cell, not any text: the summary above it names the debito too, and this line is // about the form laying out its casillas. await expect(page.getByRole('cell', { name: 'Debito fiscal', exact: true })).toBeVisible(); await expect(page.getByText(es['decl.preview.draftMark'])).toBeVisible(); // D3: approving asks once, restating the number. await page.getByRole('button', { name: es['decl.approve'], exact: true }).click(); const confirm = page.getByRole('dialog'); await expect(confirm).toBeVisible(); await expect(confirm.getByText(/Vas a aprobar tu 120 de/)).toBeVisible(); await confirm.getByRole('button', { name: es['decl.approve'], exact: true }).click(); // The checklist replaces the approve footer. await expect(page.getByRole('heading', { name: es['decl.checklist.title'] })).toBeVisible(); await expect(page.getByText(es['decl.preview.draftMark'])).toBeHidden(); // Every casilla is one tap from the clipboard. await expect(page.getByRole('button', { name: es['decl.checklist.copyValue'] }).first()).toBeVisible(); // The PDF is a real one. const download = page.waitForEvent('download'); await page.getByRole('link', { name: es['decl.downloadPdf'] }).click(); const file = await download; expect(file.suggestedFilename()).toMatch(/^formulario-120-\d{4}-\d{2}\.pdf$/); const stream = await file.createReadStream(); const chunks: Buffer[] = []; for await (const chunk of stream) chunks.push(chunk as Buffer); const bytes = Buffer.concat(chunks); expect(bytes.subarray(0, 5).toString()).toBe('%PDF-'); expect(bytes.byteLength).toBeGreaterThan(1000); // "Ya la presente" is the user telling us, and it sticks. await page.getByRole('button', { name: es['decl.checklist.done'] }).click(); await expect(page.getByRole('heading', { name: es['decl.filed.title'] })).toBeVisible(); await page.reload(); await expect(page.getByRole('heading', { name: es['decl.filed.title'] })).toBeVisible(); }); test('generating a period that is already approved is refused', async ({ page }) => { await signIn(page); await page.goto('/es/declaraciones'); // Whatever is on the list, an approved row is not offered for regeneration. const approved = page.getByRole('link').filter({ hasText: es['decl.status.approved'] }).first(); await expect(approved).toBeVisible(); await approved.click(); await expect(page.getByRole('button', { name: es['decl.approve'], exact: true })).toBeHidden(); }); }); /** Read only, so it runs on a phone viewport too. */ test.describe('the declarations list', () => { /** * The scan button used to float over the sticky approve footer on a phone and swallow * the tap. Detail screens have their own primary action, so it is not shown there. */ test('does not float the scan button over a detail screen', async ({ page }) => { await signIn(page); await page.goto('/es/declaraciones'); await expect(page.getByRole('link', { name: es['scan.fab'] })).toBeVisible(); // Scoped to main: the tab bar is a list too. await page.locator('main').getByRole('listitem').first().getByRole('link').first().click(); await expect(page.getByRole('heading', { name: es['decl.summary.title'] })).toBeVisible(); await expect(page.getByRole('link', { name: es['scan.fab'] })).toBeHidden(); }); test('groups by year and shows the form, period and amount', async ({ page }) => { await signIn(page); await page.goto('/es/declaraciones'); await expect(page.getByRole('heading', { name: es['decl.title'] })).toBeVisible(); const rows = page.locator('main').getByRole('listitem'); await expect(rows.first()).toBeVisible(); await expect(rows.first()).toContainText('120'); await expect(rows.first()).toContainText(/Gs\. [\d.]+/); }); test('is reachable from the tab bar', async ({ page }) => { await signIn(page); await page.getByRole('link', { name: es['nav.declaraciones'], exact: true }).click(); await expect(page).toHaveURL(/\/es\/declaraciones$/); }); });