Diploma A4 print-first (§Design pct.9): certificat la final campanie

Buton "Vezi diploma" pe finale (+ "Joaca din nou"). Overlay #diploma:
certificat A4 portret alb, chenar dublu accent, titlu serif (singurul),
numele copilului = cel mai mare element.

- buildDiploma(): rand de stele per camera (roomStars[], persistat in resume;
  camere sarite = 🔒 "sarita"), cuvant magic in dale (lacate pt sarite),
  footer = data + "creat de {creator}" + marcaj auriu "timpul a expirat"
- camp builder nou: creator ("Creat de")
- @media print izoleaza #diploma (rest visibility:hidden, margin 20mm,
  print-color-adjust:exact)
- exemplu-campanie.html regenerat

Smoke 31/31 (test nou "diploma": nume/titlu/stele/cuvant/creator/inapoi) +
screenshot scratch/diploma.png (A4, camera sarita, footer expirat).
Cluster T10/PR2 complet (D7 + Timer + Muzica + Diploma). Ramas Etapa 2: Adventure Mode v0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-13 20:20:59 +00:00
parent d8cb515545
commit 023df382f0
7 changed files with 308 additions and 13 deletions

View File

@@ -5,7 +5,7 @@ Smoke + regresie + campanie E2E pentru jocurile generate. Verifică faptic: fiec
până la ecranul final, fără erori de consolă.
## Ownership
- `tests/smoke.mjs` — unicul fișier de teste (~30 teste).
- `tests/smoke.mjs` — unicul fișier de teste (~31 teste).
- `playwright.config.mjs` (la root, **gitignored**) — config dev.
## Local Contracts
@@ -18,9 +18,9 @@ până la ecranul final, fără erori de consolă.
fiecare test asertează `errors.length === 0` la final.
- **Tag-uri:** `@regresie` (16 — exemplu-*.html + edge cases + mobil 320px + regenerare via gameHTML +
stil top-level invalid la import + bomberman gameplay + bomberman rază/powerup-uri) și `@campanie`
(16 — intro→hartă→camere→final, resume, cameră moartă, idempotență ușă, `$`/`$&`, beep, mobil,
audio S1, voce/narațiune D10, a11y tap/aria/reduced-motion, navigare overworld, timer calm T10, muzica ambient T10).
- **Status țintă: 30/30 PASS.**
(17 — intro→hartă→camere→final, resume, cameră moartă, idempotență ușă, `$`/`$&`, beep, mobil,
audio S1, voce/narațiune D10, a11y tap/aria/reduced-motion, navigare overworld, timer calm T10, muzica ambient T10, diploma A4).
- **Status țintă: 31/31 PASS.**
## Work Guidance
- După modificări la motoare (`escape-builder.html`): rulează suita completă; extinde `@regresie` dacă
@@ -29,7 +29,7 @@ până la ecranul final, fără erori de consolă.
## Verification
```bash
npx playwright test tests/smoke.mjs # 30/30
npx playwright test tests/smoke.mjs # 31/31
npx playwright test tests/smoke.mjs --grep @regresie
npx playwright test tests/smoke.mjs --grep @campanie
```

View File

@@ -791,6 +791,63 @@ test.describe('Campanie E2E @campanie', () => {
expect(errors, errors.join('\n')).toHaveLength(0);
});
test('diploma — „Vezi diploma" arata certificat A4 cu nume/stele-per-camera/cuvant/creator, inapoi (§Design pct.9)',
async ({ page }) => {
test.setTimeout(120000);
const errors = trackErrors(page);
const cfg = campaignCfg(3, 'classic');
cfg.player = 'Maria';
cfg.creator = 'Doamna Ana';
const tmpPath = await writeCampaignHtml(page, cfg, 'diploma');
const gp = await page.context().newPage();
const gameErrors = trackErrors(gp);
try {
await gp.goto('file://' + tmpPath);
await gp.locator('#btn-start').click();
for (let i = 0; i < 3; i++) {
await enterRoom(gp, i);
await solveRoom(gp, 'classic', 'r' + (i + 1));
}
await gp.waitForFunction(() => document.getElementById('finale')?.classList.contains('show'),
null, { timeout: 10000 });
// Buton „Vezi diploma" → diploma vizibila, finale ascunsa
await gp.locator('#btn-diploma').click();
await expect(gp.locator('#diploma')).toBeVisible();
await expect(gp.locator('#finale')).not.toBeVisible();
// Numele copilului (cel mai mare element) + titlul jocului
await expect(gp.locator('#dipl-name')).toHaveText('Maria');
await expect(gp.locator('#dipl-game')).toContainText('Test Campanie');
// Rand de stele per camera: 3 randuri, fiecare cu ★ (rezolvate, nu sarite)
await expect(gp.locator('#dipl-rooms .dipl-room')).toHaveCount(3);
const firstRoom = await gp.locator('#dipl-rooms .dipl-room .rstars').first().innerText();
expect(firstRoom).toMatch(/[★☆]{3}/);
// Cuvantul magic in dale (3 litere colectate, A B C)
await expect(gp.locator('#dipl-word span')).toHaveCount(3);
// Footer: data + „creat de Doamna Ana"
await expect(gp.locator('#dipl-footer')).toContainText('creat de Doamna Ana');
// Butonul de print exista (nu il apasam — window.print blocheaza headless)
await expect(gp.locator('#dipl-print')).toBeVisible();
// Inapoi → finale din nou
await gp.locator('#dipl-back').click();
await expect(gp.locator('#finale')).toBeVisible();
await expect(gp.locator('#diploma')).not.toBeVisible();
} finally {
await gp.close();
try { unlinkSync(tmpPath); } catch (_) {}
}
expect(gameErrors, 'Game errors:\n' + gameErrors.join('\n')).toHaveLength(0);
expect(errors, errors.join('\n')).toHaveLength(0);
});
// ─────────────────────────────────────────────────────────────────────
// Test 3: Camera moartă — timeout 4s → skip-banner + cod eroare
// ─────────────────────────────────────────────────────────────────────