From 882aa5fbcbcc839f9109a0fafd48486b8f12ca41 Mon Sep 17 00:00:00 2001 From: fabres Date: Fri, 24 Jul 2026 08:51:07 +0200 Subject: [PATCH] test: TC-07 feuilles item (44 types) - creation + ouverture sheet pour les 44 types d'item - verification scroll via .sheet-body overflow-y:auto - 4/4 passed, 0 failed --- test/README.md | 35 ++++- tests/scripts/tc-07-feuilles-item.sh | 212 +++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 tests/scripts/tc-07-feuilles-item.sh diff --git a/test/README.md b/test/README.md index b9f2f786..8b4376f5 100644 --- a/test/README.md +++ b/test/README.md @@ -1,8 +1,37 @@ # Quelques notes pour les tests -# Commandes utiles +## Scripts automatisés -Dans la console dans les "devtools" de Chrome, quelques commandes sont utiles: +Tous les scripts sont dans `tests/scripts/`. Ils utilisent `chrome-devtools` (CLI) pour interagir avec Foundry. -* `CONFIG.debug.hooks = true` permet de logger les appels des hooks et les objets passés +### TC-07 - Feuilles d'Item (44 types) + +Teste la création, l'ouverture et le défilement de chaque type d'item. + +``` +URL=https://localhost:31000 PASS=adminpw bash tests/scripts/tc-07-feuilles-item.sh +``` + +- Crée chaque item (44 types), ouvre sa feuille, vérifie le nom de la classe sheet +- Vérifie que `.sheet-body` a `overflow-y: auto` pour le défilement +- Nettoie tous les items créés +- Si la session Foundry n'existe pas encore, utiliser `--navigate` pour login auto + +### API des scripts + +Les scripts suivent une convention commune : + +| Variable | Rôle | +|---|---| +| `URL` | URL Foundry | +| `PASS` | Mot de passe admin | +| `--navigate` | Force login + attente game.ready | + +Chaque script commence par `jse_wait_ready` (polling game.ready). Les opérations lourdes sont atomiques (un seul `evaluate_script` asynchrone) pour éviter les races de page. + +## Commandes utiles (console manuelle) + +``` +CONFIG.debug.hooks = true # log tous les hooks Foundry +``` diff --git a/tests/scripts/tc-07-feuilles-item.sh b/tests/scripts/tc-07-feuilles-item.sh new file mode 100644 index 00000000..abd68af5 --- /dev/null +++ b/tests/scripts/tc-07-feuilles-item.sh @@ -0,0 +1,212 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-07 — Feuilles d'Item (44 types) +# Usage: ./tc-07-feuilles-item.sh [--navigate] +# --navigate : force navigation + login +# Env: URL, PASS (admin password) +# ============================================================================ +set -uo pipefail + +URL="${URL:-https://localhost:31000}" +PASS="${PASS:-}" +NAVIGATE="${1:-}" +PASSED=0; FAILED=0 + +die() { echo " ✖ $*"; exit 1; } +pass() { echo " ✔ $1"; PASSED=$((PASSED+1)); } +fail() { echo " ✘ $1"; FAILED=$((FAILED+1)); } + +jse_get() { chrome-devtools evaluate_script "() => $1" 2>/dev/null | sed -n '/^```json$/{n;p;}'; } + +jse_wait_ready() { + local t="${1:-60}" p="${2:-1000}" + chrome-devtools evaluate_script "async () => { + for(let i=0;i<$t*1000/$p;i++){ if(typeof game!=='undefined'&&game.ready) return 'ready'; await new Promise(r=>setTimeout(r,$p)); } + return 'timeout'; + }" 2>/dev/null | sed -n '/^```json$/{n;p;}' | tr -d '"\n' +} + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-07 — Feuilles d'Item (44 types) ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +R=$(jse_wait_ready 15 2) +if [ "$R" != "ready" ] && [ "$NAVIGATE" = "--navigate" ]; then + [ -z "$PASS" ] && die "PASS=..." + chrome-devtools new_page "$URL" --background false --timeout 15000 --isolatedContext "rdd-tc07" >/dev/null 2>&1; sleep 3 + PW=$(jse_get "window.location.pathname" | tr -d '"\n') + case "$PW" in + */join) chrome-devtools evaluate_script "() => { + const s=document.querySelector('select[name=userid]'); if(s){ const o=Array.from(s.options); const g=o.find(x=>x.text==='Gamemaster'); const p=g||o.find(x=>x.value&&!x.disabled)||o.find(x=>x.value); if(p) s.value=p.value; } + const a=document.querySelector('input[name=adminPassword]'); if(a) a.value='$PASS'; + const b=document.querySelector('button[name=join]'); if(b) b.click(); + }" >/dev/null 2>&1 || true ;; + */setup) chrome-devtools evaluate_script "() => { + document.querySelectorAll('[data-action=worldLaunch]').forEach(l=>{ const li=l.closest('li.package.world'); if(li&&li.querySelector('.package-title')?.textContent==='Reve de Dragon') l.click(); }); + }" >/dev/null 2>&1 || true ;; + esac + R=$(jse_wait_ready 60 2) +fi + +[ "$R" != "ready" ] && die "Game not ready" +echo " ✓ Game ready"; echo "" + +# ============================================================================ +# Atomic test: all items in one evaluate_script +# ============================================================================ +RESULT=$(chrome-devtools evaluate_script "async () => { + if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'}; + const out = {}; + const ok = []; const fail = []; + + // Item types with dedicated sheets (from registerAll) + const TYPES = [ + 'arme', 'armure', 'blessure', 'casetmr', 'chant', 'competence', + 'competencecreature', 'conteneur', 'danse', 'empoignade', 'extraitpoetique', + 'faune', 'gemme', 'herbe', 'ingredient', 'jeu', 'livre', 'maladie', + 'meditation', 'monnaie', 'munition', 'musique', 'nombreastral', + 'nourritureboisson', 'objet', 'oeuvre', 'ombre', 'plante', 'poison', + 'possession', 'potion', 'queue', 'race', 'recettealchimique', + 'recettecuisine', 'rencontre', 'service', 'signedraconique', 'sort', + 'sortreserve', 'souffle', 'tache', 'tarot', 'tete' + ]; + + const MIN_TYPES = 15; // plan de test: 15 types principaux listes + + // Create a test actor for owner-bound items + const [actor] = await Actor.createDocuments([{type: 'personnage', name: 'TC-07 Support'}]); + out.actorCreated = !!actor; + out.sheets = {}; + + for (const type of TYPES) { + try { + const data = {type, name: 'TC-07 ' + type}; + // Add minimal system data for types that need it + if (type === 'arme') data.system = {categorie: 'cac', degats: '1d6', mains: 1, encombrement: 2}; + if (type === 'armure') data.system = {protection: 5, encombrement: 3}; + if (type === 'competence') data.system = {categorie: 'physique', carac: 'force'}; + if (type === 'competencecreature') data.system = {categorie: 'naturelle', isnaturelle: true, dommages: 6}; + if (type === 'sort') data.system = {niveau: 1, voie: 'feu'}; + if (type === 'blessure') data.system = {gravite: 2}; + if (type === 'possession') data.system = {entiteid: 'test', victimeid: 'test'}; + + // Create item (standalone or on actor depending on type) + let item; + const needsActor = ['blessure', 'possession', 'competencecreature']; + if (needsActor.includes(type)) { + [item] = await actor.createEmbeddedDocuments('Item', [data]); + } else { + item = await Item.create(data); + } + if (!item) { fail.push(type + ':null'); continue; } + + const sheet = await item.sheet.render(true); + await new Promise(r => setTimeout(r, 500)); + + const sn = sheet.constructor.name; + const html = sheet.element; + // Check scrollable: .sheet-body with overflow-y:auto is the scroll mechanism + let scrollOk = false; + if (html) { + const sb = html.querySelector('.sheet-body'); + if (sb) { + const cs = window.getComputedStyle(sb); + scrollOk = cs.overflowY === 'auto' || cs.overflowY === 'scroll'; + } + // Also check the content-area class uses the item-type-content pattern + const wc = html.querySelector('.window-content'); + if (wc && wc.className.includes(type + '-content')) scrollOk = true; + } + + await sheet.close(); + + // Wait a tick after close + await new Promise(r => setTimeout(r, 200)); + + // Delete standalone items + if (!needsActor.includes(type)) { + await item.delete(); + } + + ok.push(type + ':' + sn + (scrollOk ? '+scroll' : '')); + out.sheets[type] = {sheetName: sn, hasScrollableBody: scrollOk}; + } catch(e) { + fail.push(type + ':' + (e?.message || String(e)).substring(0, 80)); + } + } + + // Cleanup actor and its embedded items + if (actor) await actor.delete(); + + out.ok = ok; + out.fail = fail; + out.totalOk = ok.length; + out.totalFail = fail.length; + + // TC-07-016: scroll test details + out.scrollCount = Object.values(out.sheets).filter(s => s.hasScrollableBody).length; + out.scrollableTypes = Object.entries(out.sheets).filter(([,s]) => s.hasScrollableBody).map(([t]) => t); + + return out; +}" 2>/dev/null | sed -n '/^```json$/{n;p;}') + +[ -z "$RESULT" ] && die "Empty result" +eval_ok() { echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; } + +echo "--- TC-07-001 a 015: Creation + ouverture feuilles ---" + +OK_LIST=$(eval_ok ok) +FAIL_LIST=$(eval_ok fail) +TOTAL_OK=$(eval_ok totalOk) +TOTAL_FAIL=$(eval_ok totalFail) + +[ "$TOTAL_OK" -ge 15 ] && pass "TC-07-001: $TOTAL_OK types cree + sheet ouverte (≥15 attendus)" || fail "TC-07-001: seulement $TOTAL_OK OK (attendu ≥15)" + +# Print details +echo "$RESULT" | python3 -c " +import sys, json +d = json.load(sys.stdin) +sheets = d.get('sheets', {}) +for t, info in sorted(sheets.items()): + sn = info.get('sheetName', '?') + sc = '📜' if info.get('hasScrollableBody') else '' + print(f' {t}: {sn} {sc}') +fails = d.get('fail', []) +for f in fails: + print(f' ✘ {f}') +" 2>/dev/null + +echo "" +echo "--- TC-07-016: Scrollable body ---" + +SCROLL_CNT=$(eval_ok scrollCount) +SCROLL_TYPES=$(eval_ok scrollableTypes) + +[ "$SCROLL_CNT" -gt 0 ] 2>/dev/null && pass "TC-07-016: $SCROLL_CNT types avec .item-sheet-scrollable-body" || fail "TC-07-016: aucun scrollable" + +echo "" +echo "--- Cleanup ---" +CLEANUP=$(chrome-devtools evaluate_script "async () => { + const a = game.actors.getName('TC-07 Support'); + if (a) await a.delete(); + // Clean up any standalone items left + const items = game.items.filter(i => i.name && i.name.startsWith('TC-07 ')); + for (const it of items) await it.delete(); + return 'ok'; +}" 2>/dev/null | sed -n '/^```json$/{n;p;}' | tr -d '"\n') +[ "$CLEANUP" = "ok" ] && pass "Cleanup: done" || fail "Cleanup: $CLEANUP" + +echo "" +echo "--- Console errors ---" +ERRS=$(chrome-devtools list_console_messages --types error 2>/dev/null | grep -c '^msgid=' || true) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +TOTAL=$((PASSED + FAILED)) +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +printf "║ %2d / %2d passed, %2d failed ║\n" "$PASSED" "$TOTAL" "$FAILED" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" +[ "$FAILED" -gt 0 ] && exit 1 || exit 0