Known improvements: dedup HUD letters-bar + validare stil import
Pasa de igiena (T8/T5/D8). Majoritatea erau deja livrate (persist guard D12, esc/letter D13, validare 0 puzzle). Reale ramase: - updateHud arcade/point NU erau identice (arcade: vieti/dusmani/bombe/raza; point: obiecte). Partea duplicata reala (scor + bara litere castigate) extrasa in SNIP.hudJs -> hudLetters(isSolved); isSolved(j) difera per motor (doorsSolved vs solvedFlags). Injectat in ambele; demo-uri regenerate. - Stil top-level invalid la import: TOP_STYLES guard -> fallback classic + alert; idem la load din storage corupt. Test nou (smoke 28/28). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,9 +20,9 @@ sursa de adevăr tehnică pentru agenți.
|
|||||||
python3 -m http.server 8000
|
python3 -m http.server 8000
|
||||||
|
|
||||||
# Teste (Playwright; fără package.json commitat — vezi tests/AGENTS.md):
|
# Teste (Playwright; fără package.json commitat — vezi tests/AGENTS.md):
|
||||||
npx playwright test tests/smoke.mjs # suita completă: 27/27
|
npx playwright test tests/smoke.mjs # suita completă: 28/28
|
||||||
npx playwright test tests/smoke.mjs --grep @regresie # regresie: 15
|
npx playwright test tests/smoke.mjs --grep @regresie # regresie: 16
|
||||||
npx playwright test tests/smoke.mjs --grep @campanie # campanie E2E: 12
|
npx playwright test tests/smoke.mjs --grep @campanie # campanie E2E: 14
|
||||||
```
|
```
|
||||||
|
|
||||||
## Durable Rules (repo-wide)
|
## Durable Rules (repo-wide)
|
||||||
|
|||||||
44
TODOS.md
44
TODOS.md
@@ -18,7 +18,7 @@ Referință plan complet: `~/.gstack/projects/romfast-escape-builder/ceo-plans/2
|
|||||||
- [x] **Audit a11y motoare** — LIVRAT (vezi §dedicată mai jos). Smoke 26/26.
|
- [x] **Audit a11y motoare** — LIVRAT (vezi §dedicată mai jos). Smoke 26/26.
|
||||||
|
|
||||||
**PR2 livrat (2026-06-13):** audio camere `651025b`, voce `da93d84`, unificare `ab11089`, a11y (acest commit).
|
**PR2 livrat (2026-06-13):** audio camere `651025b`, voce `da93d84`, unificare `ab11089`, a11y (acest commit).
|
||||||
Rămas din Etapa 2: D7 (migrare classic pe libJS+SNIP) + muzică timer (T10) + Adventure Mode v0.
|
Rămas din Etapa 2: muzică timer (T10) + Adventure Mode v0. (D7 LIVRAT — vezi §dedicată mai jos.)
|
||||||
|
|
||||||
### [x] Bomberman polish (feedback user 2026-06-13) — LIVRAT
|
### [x] Bomberman polish (feedback user 2026-06-13) — LIVRAT
|
||||||
Trei probleme raportate + o lipsă, toate în `gameArcade` (`escape-builder.html`):
|
Trei probleme raportate + o lipsă, toate în `gameArcade` (`escape-builder.html`):
|
||||||
@@ -134,11 +134,35 @@ Acum trăiește o singură dată în `libJS.campaignDone()` (lângă `roomReady`
|
|||||||
Verificat: smoke 25/25 (terminal standalone test 2 + camere terminal în campanie E2E test 1).
|
Verificat: smoke 25/25 (terminal standalone test 2 + camere terminal în campanie E2E test 1).
|
||||||
Referință: planul §Etapa 2 pct. 1; D7.
|
Referință: planul §Etapa 2 pct. 1; D7.
|
||||||
|
|
||||||
### [ ] D7 rămas: migrarea `gameClassic` pe `libJS+SNIP`
|
### [x] D7: migrarea `gameClassic` pe `libJS` — LIVRAT (2026-06-13)
|
||||||
- Classic (escape-builder.html:451) e singurul motor bespoke: propriul `totalStars`, `beep`,
|
Classic era ultimul motor bespoke (propriul `CFG`/`norm`/`beep`/`confetti`, star-logic inline,
|
||||||
inline `finalWord` (dublat de 2 ori în `next()`), propriul modal final `#sFinal`.
|
`finalWord` dublat, payload `parent.nextRoom` inline). Acum injectează `libJS(cfg)` și folosește
|
||||||
- După migrare: classic folosește `libJS.campaignDone()` + `SNIP` ca celelalte 4 → 5/5 uniform.
|
`checkAnswer`/`starsFor`/`finalWord`/`choiceOpts`/`campaignDone`/`roomReady`/`onerror` din libJS
|
||||||
- Necesită regresie manuală pe classic standalone (e demo-ul implicit, cel mai vizibil).
|
ca celelalte 4 motoare → **5/5 uniform** pe contractul de finalizare.
|
||||||
|
- **Decizie de design (păstrată din unificarea `campaignDone`):** UI-ul bespoke al classicului
|
||||||
|
(card `sStart`/`sGame`/`sFinal`) RĂMÂNE. NU am forțat modalul/overlay-ul `SNIP.modal`/`SNIP.final`
|
||||||
|
— classic e quiz inline (nu deschide puzzle-uri dintr-o hartă), iar `#sFinal` e on-theme; forțarea
|
||||||
|
SNIP-ului ar fi regresie vizuală pe demo-ul implicit (cel mai vizibil). Aceeași logică ca terminalul
|
||||||
|
cu finale CRT. „Migrare pe libJS+SNIP" din formularea inițială = în practică migrare pe **libJS**;
|
||||||
|
SNIP-ul modal nu se aplică unui motor non-modal (vezi și terminalul, care nu folosește SNIP.modal).
|
||||||
|
- net −70 linii duplicate; `campaignDone()` rămâne singura sursă a payload-ului `nextRoom`.
|
||||||
|
- `exemplu-clasic.html` regenerat (celelalte demo-uri byte-identice → classic a fost singura atingere).
|
||||||
|
- Verificat: smoke 28/28 (regresie classic standalone test #1 + campanie E2E cu classic ca odaie test #14).
|
||||||
|
Commit: `bfe9be2`.
|
||||||
|
|
||||||
|
### [x] Known improvements — pasă de igienă (2026-06-13)
|
||||||
|
Auditate faptic. Cele mai multe erau **deja livrate** în PR-uri anterioare:
|
||||||
|
- `persist()` try/catch → DEJA (escape-builder.html:211, D12).
|
||||||
|
- `esc(L)` la point SVG → DEJA rezolvat la SURSĂ: `cleanState()` normalizează `letter` la 1 caracter
|
||||||
|
alfanumeric (linia ~407, D13) → un `<` nu mai poate ajunge în scenă.
|
||||||
|
- Validare 0 puzzle-uri → DEJA: export blocat cu alert + preview cu mesaj ghidant (🚪).
|
||||||
|
- `updateHud` „identic" arcade/point → NU era identic (arcade arată vieți/dușmani/bombe/rază; point
|
||||||
|
arată obiecte). REAL duplicat: scor + bara de litere câștigate → extras în `SNIP.hudJs`
|
||||||
|
(`hudLetters(isSolved)`, `isSolved(j)` diferă per motor: doorsSolved vs solvedFlags). Injectat în
|
||||||
|
ambele; demo-uri arcade+point regenerate.
|
||||||
|
- **Stil top-level invalid la import** (singurul gap rămas, T5/D8) → `TOP_STYLES` guard: fallback la
|
||||||
|
`classic` + alert „Stil necunoscut …" la import; idem la load din storage corupt. Test nou smoke
|
||||||
|
(`stil top-level necunoscut → fallback classic + avertisment`).
|
||||||
|
|
||||||
### [x] Audit a11y motoare existente — LIVRAT (sub harness Playwright)
|
### [x] Audit a11y motoare existente — LIVRAT (sub harness Playwright)
|
||||||
Auditat faptic (măsurat, nu presupus). Ce era DEJA OK (din restyle S3, nemodificat):
|
Auditat faptic (măsurat, nu presupus). Ce era DEJA OK (din restyle S3, nemodificat):
|
||||||
@@ -184,8 +208,6 @@ Referință: §Design pct. 13 (TD5, PR2); D19 din plan.
|
|||||||
|
|
||||||
## Known improvements (oricând)
|
## Known improvements (oricând)
|
||||||
|
|
||||||
- **`updateHud` duplicat**: arcade linia 1003 și point linia 1283 au funcții identice → consolidat în `SNIP` (T8 din plan, igienă PR1).
|
Toate cele listate inițial au fost rezolvate — vezi „[x] Known improvements — pasă de igienă" mai sus
|
||||||
- **`persist()` fără try/catch**: builder-ul poate crăpa pe storage plin → guard (D12, T8).
|
(updateHud dedup în `SNIP.hudJs`, persist guard D12, esc/letter D13, validare 0 puzzle, stil invalid la
|
||||||
- **`esc(L)` la inserția innerHTML din point** (:1274): un `<` în câmpul `letter` strică scena SVG (D13, T8).
|
import T5/D8). Adaugă aici lucruri noi pe măsură ce apar.
|
||||||
- **Validare 0 puzzle-uri**: export și preview blocate cu mesaj ghidant (T5).
|
|
||||||
- **Stil invalid la import JSON**: avertisment în builder + rotație automată (T5, D8).
|
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ const STORAGE_KEY = 'escape-builder-v1';
|
|||||||
|
|
||||||
const CAMPAIGN_ROTATION = ['classic', 'terminal', 'arcade', 'chat', 'point'];
|
const CAMPAIGN_ROTATION = ['classic', 'terminal', 'arcade', 'chat', 'point'];
|
||||||
const CAMPAIGN_STYLE_NAMES = { classic: 'Clasic', terminal: 'Terminal Retro', arcade: 'Arcade Pixel', chat: 'Story Chat', point: 'Point-and-Click' };
|
const CAMPAIGN_STYLE_NAMES = { classic: 'Clasic', terminal: 'Terminal Retro', arcade: 'Arcade Pixel', chat: 'Story Chat', point: 'Point-and-Click' };
|
||||||
|
/* Stiluri top-level valide (gameHTML rutează pe ele); orice altceva → fallback classic (T5, D8) */
|
||||||
|
const TOP_STYLES = ['classic', 'terminal', 'arcade', 'chat', 'point', 'campaign'];
|
||||||
|
|
||||||
const defaultState = () => ({
|
const defaultState = () => ({
|
||||||
title: 'Comoara ascunsa',
|
title: 'Comoara ascunsa',
|
||||||
@@ -202,6 +204,7 @@ function normalizePuzzle(p) {
|
|||||||
const blankPuzzle = () => normalizePuzzle({ title: '', type: 'free', question: '', answer: '', tfAnswer: 'Adevarat', choices: '', hint: '', letter: '', style: '' });
|
const blankPuzzle = () => normalizePuzzle({ title: '', type: 'free', question: '', answer: '', tfAnswer: 'Adevarat', choices: '', hint: '', letter: '', style: '' });
|
||||||
|
|
||||||
let state = Object.assign(defaultState(), load() || {});
|
let state = Object.assign(defaultState(), load() || {});
|
||||||
|
if (!TOP_STYLES.includes(state.style)) state.style = 'classic'; /* storage corupt → fallback */
|
||||||
if (Array.isArray(state.puzzles)) state.puzzles = state.puzzles.map(normalizePuzzle);
|
if (Array.isArray(state.puzzles)) state.puzzles = state.puzzles.map(normalizePuzzle);
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
@@ -383,8 +386,11 @@ $('#fileLoad').addEventListener('change', e => {
|
|||||||
const data = JSON.parse(txt);
|
const data = JSON.parse(txt);
|
||||||
if (!Array.isArray(data.puzzles)) throw new Error('format');
|
if (!Array.isArray(data.puzzles)) throw new Error('format');
|
||||||
state = Object.assign(defaultState(), data);
|
state = Object.assign(defaultState(), data);
|
||||||
|
let styleWarn = '';
|
||||||
|
if (!TOP_STYLES.includes(state.style)) { styleWarn = ' Stil necunoscut „' + state.style + '" — am rotit la „Clasic".'; state.style = 'classic'; }
|
||||||
state.puzzles = state.puzzles.map(normalizePuzzle);
|
state.puzzles = state.puzzles.map(normalizePuzzle);
|
||||||
renderGlobals(); renderPuzzles(); onChange();
|
renderGlobals(); renderPuzzles(); onChange();
|
||||||
|
if (styleWarn) alert('Proiect incarcat.' + styleWarn);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('Fisierul nu este un proiect valid de escape room.');
|
alert('Fisierul nu este un proiect valid de escape room.');
|
||||||
}
|
}
|
||||||
@@ -830,6 +836,19 @@ SNIP.finalJs = `function showFinal(){
|
|||||||
}
|
}
|
||||||
el('fAgain').onclick = function(){ location.reload(); };`;
|
el('fAgain').onclick = function(){ location.reload(); };`;
|
||||||
|
|
||||||
|
/* HUD partajat (arcade + point): scor + bara de litere câștigate. isSolved(j)→bool
|
||||||
|
diferă per motor (doorsSolved vs solvedFlags) → injectat ca funcție (T8). */
|
||||||
|
SNIP.hudJs = `function hudLetters(isSolved){
|
||||||
|
el('hudStars').textContent = totalStars + ' \\u2605';
|
||||||
|
var hb = el('hudLetters'); hb.innerHTML = '';
|
||||||
|
for (var j = 0; j < CFG.puzzles.length; j++){
|
||||||
|
var L = (CFG.puzzles[j].letter || '').trim(); if (!L) continue;
|
||||||
|
var s = document.createElement('span');
|
||||||
|
if (isSolved(j)){ s.textContent = L.toUpperCase(); s.className = 'won'; } else s.textContent = '?';
|
||||||
|
hb.appendChild(s);
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
/* ---------- motor: terminal retro ---------- */
|
/* ---------- motor: terminal retro ---------- */
|
||||||
|
|
||||||
function gameTerminal(cfg) {
|
function gameTerminal(cfg) {
|
||||||
@@ -1111,9 +1130,7 @@ function updateHud(){
|
|||||||
var solved = puzzleProgress ? puzzleProgress.doorsSolved.filter(Boolean).length : 0;
|
var solved = puzzleProgress ? puzzleProgress.doorsSolved.filter(Boolean).length : 0;
|
||||||
var alive = enemies ? enemies.filter(function(e){ return e.alive; }).length : 0;
|
var alive = enemies ? enemies.filter(function(e){ return e.alive; }).length : 0;
|
||||||
el('hudStep').textContent = '\\u2764\\ufe0f ' + lives + ' \\ud83d\\udc7e ' + alive + ' \\ud83d\\udd13 ' + solved + '/' + N + ' \\ud83d\\udca3' + (maxBombs || 1) + ' \\ud83d\\udd25' + (bombRange || 1);
|
el('hudStep').textContent = '\\u2764\\ufe0f ' + lives + ' \\ud83d\\udc7e ' + alive + ' \\ud83d\\udd13 ' + solved + '/' + N + ' \\ud83d\\udca3' + (maxBombs || 1) + ' \\ud83d\\udd25' + (bombRange || 1);
|
||||||
el('hudStars').textContent = totalStars + ' \\u2605';
|
hudLetters(function(j){ return puzzleProgress && puzzleProgress.doorsSolved[j]; });
|
||||||
var hb = el('hudLetters'); hb.innerHTML = '';
|
|
||||||
for (var j = 0; j < N; j++){ var L = (CFG.puzzles[j].letter || '').trim(); if (!L) continue; var s = document.createElement('span'); if (puzzleProgress && puzzleProgress.doorsSolved[j]){ s.textContent = L.toUpperCase(); s.className = 'won'; } else s.textContent = '?'; hb.appendChild(s); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----- Bombe + explozii în lanț ----- */
|
/* ----- Bombe + explozii în lanț ----- */
|
||||||
@@ -1280,6 +1297,7 @@ window.__game = {
|
|||||||
getTile: function(x, y){ return map && map[y] ? map[y][x] : -1; }
|
getTile: function(x, y){ return map && map[y] ? map[y][x] : -1; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
${SNIP.hudJs}
|
||||||
${SNIP.modalJs}
|
${SNIP.modalJs}
|
||||||
${SNIP.finalJs}
|
${SNIP.finalJs}
|
||||||
init();
|
init();
|
||||||
@@ -1527,17 +1545,9 @@ function onSolved(i){
|
|||||||
|
|
||||||
function updateHud(){
|
function updateHud(){
|
||||||
el('hudStep').textContent = 'Obiecte: ' + solvedCount + '/' + N;
|
el('hudStep').textContent = 'Obiecte: ' + solvedCount + '/' + N;
|
||||||
el('hudStars').textContent = totalStars + ' \\u2605';
|
hudLetters(function(j){ return solvedFlags[j]; });
|
||||||
var hb = el('hudLetters'); hb.innerHTML = '';
|
|
||||||
for (var j = 0; j < N; j++) {
|
|
||||||
var L = (CFG.puzzles[j].letter || '').trim();
|
|
||||||
if (!L) continue;
|
|
||||||
var s = document.createElement('span');
|
|
||||||
if (solvedFlags[j]) { s.textContent = L.toUpperCase(); s.className = 'won'; }
|
|
||||||
else s.textContent = '?';
|
|
||||||
hb.appendChild(s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
${SNIP.hudJs}
|
||||||
${SNIP.modalJs}
|
${SNIP.modalJs}
|
||||||
${SNIP.finalJs}
|
${SNIP.finalJs}
|
||||||
updateHud();
|
updateHud();
|
||||||
|
|||||||
@@ -206,9 +206,7 @@ function updateHud(){
|
|||||||
var solved = puzzleProgress ? puzzleProgress.doorsSolved.filter(Boolean).length : 0;
|
var solved = puzzleProgress ? puzzleProgress.doorsSolved.filter(Boolean).length : 0;
|
||||||
var alive = enemies ? enemies.filter(function(e){ return e.alive; }).length : 0;
|
var alive = enemies ? enemies.filter(function(e){ return e.alive; }).length : 0;
|
||||||
el('hudStep').textContent = '\u2764\ufe0f ' + lives + ' \ud83d\udc7e ' + alive + ' \ud83d\udd13 ' + solved + '/' + N + ' \ud83d\udca3' + (maxBombs || 1) + ' \ud83d\udd25' + (bombRange || 1);
|
el('hudStep').textContent = '\u2764\ufe0f ' + lives + ' \ud83d\udc7e ' + alive + ' \ud83d\udd13 ' + solved + '/' + N + ' \ud83d\udca3' + (maxBombs || 1) + ' \ud83d\udd25' + (bombRange || 1);
|
||||||
el('hudStars').textContent = totalStars + ' \u2605';
|
hudLetters(function(j){ return puzzleProgress && puzzleProgress.doorsSolved[j]; });
|
||||||
var hb = el('hudLetters'); hb.innerHTML = '';
|
|
||||||
for (var j = 0; j < N; j++){ var L = (CFG.puzzles[j].letter || '').trim(); if (!L) continue; var s = document.createElement('span'); if (puzzleProgress && puzzleProgress.doorsSolved[j]){ s.textContent = L.toUpperCase(); s.className = 'won'; } else s.textContent = '?'; hb.appendChild(s); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----- Bombe + explozii în lanț ----- */
|
/* ----- Bombe + explozii în lanț ----- */
|
||||||
@@ -375,6 +373,16 @@ window.__game = {
|
|||||||
getTile: function(x, y){ return map && map[y] ? map[y][x] : -1; }
|
getTile: function(x, y){ return map && map[y] ? map[y][x] : -1; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function hudLetters(isSolved){
|
||||||
|
el('hudStars').textContent = totalStars + ' \u2605';
|
||||||
|
var hb = el('hudLetters'); hb.innerHTML = '';
|
||||||
|
for (var j = 0; j < CFG.puzzles.length; j++){
|
||||||
|
var L = (CFG.puzzles[j].letter || '').trim(); if (!L) continue;
|
||||||
|
var s = document.createElement('span');
|
||||||
|
if (isSolved(j)){ s.textContent = L.toUpperCase(); s.className = 'won'; } else s.textContent = '?';
|
||||||
|
hb.appendChild(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
var mIdx = -1, mAtt = 0, mHint = false, mCb = null;
|
var mIdx = -1, mAtt = 0, mHint = false, mCb = null;
|
||||||
el('mHintBtn').onclick = function(){ mHint = true; el('mHintText').style.display = 'block'; };
|
el('mHintBtn').onclick = function(){ mHint = true; el('mHintText').style.display = 'block'; };
|
||||||
el('mClose').onclick = function(){ el('mOverlay').style.display = 'none'; };
|
el('mClose').onclick = function(){ el('mOverlay').style.display = 'none'; };
|
||||||
|
|||||||
@@ -155,14 +155,15 @@ function onSolved(i){
|
|||||||
|
|
||||||
function updateHud(){
|
function updateHud(){
|
||||||
el('hudStep').textContent = 'Obiecte: ' + solvedCount + '/' + N;
|
el('hudStep').textContent = 'Obiecte: ' + solvedCount + '/' + N;
|
||||||
|
hudLetters(function(j){ return solvedFlags[j]; });
|
||||||
|
}
|
||||||
|
function hudLetters(isSolved){
|
||||||
el('hudStars').textContent = totalStars + ' \u2605';
|
el('hudStars').textContent = totalStars + ' \u2605';
|
||||||
var hb = el('hudLetters'); hb.innerHTML = '';
|
var hb = el('hudLetters'); hb.innerHTML = '';
|
||||||
for (var j = 0; j < N; j++) {
|
for (var j = 0; j < CFG.puzzles.length; j++){
|
||||||
var L = (CFG.puzzles[j].letter || '').trim();
|
var L = (CFG.puzzles[j].letter || '').trim(); if (!L) continue;
|
||||||
if (!L) continue;
|
|
||||||
var s = document.createElement('span');
|
var s = document.createElement('span');
|
||||||
if (solvedFlags[j]) { s.textContent = L.toUpperCase(); s.className = 'won'; }
|
if (isSolved(j)){ s.textContent = L.toUpperCase(); s.className = 'won'; } else s.textContent = '?';
|
||||||
else s.textContent = '?';
|
|
||||||
hb.appendChild(s);
|
hb.appendChild(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Smoke + regresie + campanie E2E pentru jocurile generate. Verifică faptic: fiec
|
|||||||
până la ecranul final, fără erori de consolă.
|
până la ecranul final, fără erori de consolă.
|
||||||
|
|
||||||
## Ownership
|
## Ownership
|
||||||
- `tests/smoke.mjs` — unicul fișier de teste (~27 teste).
|
- `tests/smoke.mjs` — unicul fișier de teste (~28 teste).
|
||||||
- `playwright.config.mjs` (la root, **gitignored**) — config dev.
|
- `playwright.config.mjs` (la root, **gitignored**) — config dev.
|
||||||
|
|
||||||
## Local Contracts
|
## Local Contracts
|
||||||
@@ -20,7 +20,7 @@ până la ecranul final, fără erori de consolă.
|
|||||||
bomberman gameplay + bomberman rază/powerup-uri) și `@campanie` (12 — intro→hartă→camere→final, resume,
|
bomberman gameplay + bomberman rază/powerup-uri) și `@campanie` (12 — intro→hartă→camere→final, resume,
|
||||||
cameră moartă, idempotență ușă, `$`/`$&`, beep, mobil, audio S1, voce/narațiune D10,
|
cameră moartă, idempotență ușă, `$`/`$&`, beep, mobil, audio S1, voce/narațiune D10,
|
||||||
a11y tap/aria/reduced-motion, navigare overworld).
|
a11y tap/aria/reduced-motion, navigare overworld).
|
||||||
- **Status țintă: 27/27 PASS.**
|
- **Status țintă: 28/28 PASS.**
|
||||||
|
|
||||||
## Work Guidance
|
## Work Guidance
|
||||||
- După modificări la motoare (`escape-builder.html`): rulează suita completă; extinde `@regresie` dacă
|
- 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
|
## Verification
|
||||||
```bash
|
```bash
|
||||||
npx playwright test tests/smoke.mjs # 27/27
|
npx playwright test tests/smoke.mjs # 28/28
|
||||||
npx playwright test tests/smoke.mjs --grep @regresie
|
npx playwright test tests/smoke.mjs --grep @regresie
|
||||||
npx playwright test tests/smoke.mjs --grep @campanie
|
npx playwright test tests/smoke.mjs --grep @campanie
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -404,6 +404,34 @@ test.describe('Edge cases @regresie', () => {
|
|||||||
expect(errors, 'Erori consola:\n' + errors.join('\n')).toHaveLength(0);
|
expect(errors, 'Erori consola:\n' + errors.join('\n')).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('builder: JSON cu stil top-level necunoscut → fallback classic + avertisment (T5/D8)', async ({ page }) => {
|
||||||
|
const errors = trackErrors(page);
|
||||||
|
let warned = false;
|
||||||
|
page.on('dialog', d => { if (/stil necunoscut/i.test(d.message())) warned = true; d.accept(); });
|
||||||
|
|
||||||
|
await page.goto(fileURL('escape-builder.html'));
|
||||||
|
|
||||||
|
const tmpPath = join(ROOT, 'tests', '.tmp-invalid-style.json');
|
||||||
|
writeFileSync(tmpPath, JSON.stringify({
|
||||||
|
title: 'Test stil', style: 'banana', color: '#6d28d9', charName: 'X',
|
||||||
|
story: 'S', finalMessage: 'F',
|
||||||
|
puzzles: [{ title: 'P1', type: 'free', question: 'Q?', answer: 'A', tfAnswer: 'Adevarat', choices: '', hint: '', letter: 'X' }]
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.locator('#fileLoad').setInputFiles(tmpPath);
|
||||||
|
await page.waitForTimeout(600);
|
||||||
|
} finally {
|
||||||
|
unlinkSync(tmpPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback la classic + avertisment + builder functional
|
||||||
|
await expect(page.locator('#gStyle')).toHaveValue('classic');
|
||||||
|
expect(warned, 'asteptam un alert despre stilul necunoscut').toBe(true);
|
||||||
|
await expect(page.locator('#addPuzzle')).toBeVisible();
|
||||||
|
expect(errors, 'Erori consola:\n' + errors.join('\n')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user