diff --git a/tests/scripts/tc-08-roll-v1.sh b/tests/scripts/tc-08-roll-v1.sh new file mode 100755 index 00000000..e908f75e --- /dev/null +++ b/tests/scripts/tc-08-roll-v1.sh @@ -0,0 +1,240 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-08 — Système de Jet V1 (RdDRoll) +# Usage: ./tc-08-roll-v1.sh [--navigate] +# Env: URL, PASS (admin password), PASS_USER (user password) +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-08 — Système de Jet V1 (RdDRoll) ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +# Setup: créer un acteur de test + items +# ============================================================================ +SETUP=$(jse_get_async " + const ex = game.actors.getName('TC-08 Test'); + if (ex) await ex.delete(); + const [actor] = await Actor.createDocuments([{ + type: 'personnage', name: 'TC-08 Test', + system: { + carac: { force: {value: 12}, intellect: {value: 10}, dexterite: {value: 11} }, + reve: {reve: {value: 12}, seui: {value: 12} }, + sante: { vie: {value: 12, max: 12}, endurance: {value: 20, max: 20} } + } + }]); + if (!actor) return {error: 'create failed'}; + const [comp] = await actor.createEmbeddedDocuments('Item', [{ + type: 'competence', name: 'TC-08 Escrime', + system: { categorie: 'melee', niveau: 5, xp: 0, base: 0, carac: 'force' } + }]); + const [compN] = await actor.createEmbeddedDocuments('Item', [{ + type: 'competence', name: 'TC-08 Natation', + system: { categorie: 'physique', niveau: 3, xp: 0, base: 0, carac: 'force' } + }]); + const [arme] = await actor.createEmbeddedDocuments('Item', [{ + type: 'arme', name: 'TC-08 Epee', + system: { categorie: 'cac', degats: '3', competence: 'TC-08 Escrime', competence2: '', encombrement: 2, mains: 1 } + }]); + const [sort] = await actor.createEmbeddedDocuments('Item', [{ + type: 'sort', name: 'TC-08 Lueur', + system: { niveau: 4, voie: 'Oniros', draconic: 'Oniros', coutreve: 2, description: '' } + }]); + return { actorId: actor.id, compId: comp?.id, armeId: arme?.id, sortId: sort?.id }; +") + +ERR=$(eval_ok "$SETUP" error) +[ -z "$ERR" ] || [ "$ERR" = "None" ] || die "Setup: $ERR" + +AID=$(eval_ok "$SETUP" actorId) +COMP_ID=$(eval_ok "$SETUP" compId) +ARME_ID=$(eval_ok "$SETUP" armeId) +SORT_ID=$(eval_ok "$SETUP" sortId) + +# Helper: close all rdd-roll-dialogs +close_roll_dialogs() { + jse_get_async " + const apps = Object.values(ui.windows).filter(w => w.element?.classList?.contains('rdd-roll-dialog')); + for (const a of apps) { a.rollData = a.rollData || {}; a.rollData.canClose = true; await a.close(); } + return 'closed'; + " >/dev/null 2>&1 +} + +# ============================================================================ +echo "--- TC-08-001: Jet Compétence — dialogue s'ouvre ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + await a.rollCompetence('$COMP_ID'); + await new Promise(r => setTimeout(r, 2000)); + const dlg = document.querySelector('.rdd-roll-dialog'); + const ok = !!dlg; + return { opened: ok }; + } catch(e) { return {error: e?.message || String(e)}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-08-001: dialog RdDRoll ouvert" || fail "TC-08-001: dialog KO ($ERR)" +close_roll_dialogs + +# ============================================================================ +echo "--- TC-08-002: Jet Compétence — modificateurs ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + const { RdDRoll } = await import('/systems/foundryvtt-reve-de-dragon/module/rdd-roll.js'); + const comp = a.items.get('$COMP_ID'); + const rollData = { + carac: { force: a.system.carac.force }, + selectedCarac: a.system.carac.force, + competence: comp, + diffLibre: 10, + ajustementForce: 5, + forceDiceResult: -1 + }; + const content = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.hbs', rollData); + const dialog = new RdDRoll(a, rollData, content, {classes:['rdd-roll-dialog'], width:650, height:'fit-content'}, { + name: 'comp-test', label: 'Test Comp', + callbacks: [{action: r => { r.rolled = {isSuccess: true, code: 'normale'}; }}] + }); + await dialog.render(true); + await new Promise(r => setTimeout(r, 800)); + const el = dialog.element; + const hasDiff = !!el?.querySelector('[name=diffLibre]'); + const hasCarac = !!el?.querySelector('[name=carac]'); + dialog.rollData.canClose = true; + await dialog.close(); + return { opened: !!el, hasDiff, hasCarac }; + } catch(e) { return {error: e?.message || String(e)}; } +") +HASDIFF=$(eval_ok "$R" hasDiff) +[ "$HASDIFF" = "True" ] && pass "TC-08-002: champ difficulté visible" || fail "TC-08-002: KO ($HASDIFF)" +close_roll_dialogs + +# ============================================================================ +echo "--- TC-08-004: Jet Attaque ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + const arme = a.items.get('$ARME_ID'); + if (!arme) return {error: 'no arme'}; + await a.rollArme(arme, 'competence'); + await new Promise(r => setTimeout(r, 2000)); + const dlg = document.querySelector('.rdd-roll-dialog'); + const ok = !!dlg; + return { opened: ok }; + } catch(e) { return {error: e?.message || String(e)}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-08-004: dialog attaque ouvert" || fail "TC-08-004: KO ($ERR)" +close_roll_dialogs + +# ============================================================================ +echo "--- TC-08-007: Jet Sort ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + const sort = a.items.get('$SORT_ID'); + if (!sort) return {error: 'no sort'}; + if (typeof a.rollSort === 'function') await a.rollSort(sort.id); + else if (typeof a.rollSortReserve === 'function') await a.rollSortReserve(sort.id); + await new Promise(r => setTimeout(r, 2000)); + const dlg = document.querySelector('.rdd-roll-dialog'); + const ok = !!dlg; + return { opened: ok }; + } catch(e) { return {error: e?.message || String(e)}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-08-007: dialog sort ouvert" || fail "TC-08-007: KO ($ERR)" +close_roll_dialogs + +# ============================================================================ +echo "--- TC-08-009: Jet Éthylisme ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + await a.jetEthylisme(); + await new Promise(r => setTimeout(r, 2000)); + // jetEthylisme opens RdDRollDialogEthylisme, not RdDRoll + const dlg = document.querySelector('.rdd-roll-ethylisme, .dialog-ethylisme, [class*=ethylisme]'); + const ok = !!dlg; + // Also check any new dialog + const allDialogs = document.querySelectorAll('.application.dialog'); + return { opened: ok, dialogCount: allDialogs.length }; + } catch(e) { return {error: e?.message || String(e)}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-08-009: dialog éthylisme ouvert" || fail "TC-08-009: KO ($ERR)" +close_roll_dialogs + +# ============================================================================ +echo "--- TC-08-011: Message de résultat chat ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + const { RdDRoll } = await import('/systems/foundryvtt-reve-de-dragon/module/rdd-roll.js'); + const comp = a.items.get('$COMP_ID'); + const rollData = { + carac: { force: a.system.carac.force }, + selectedCarac: a.system.carac.force, + competence: comp, + diffLibre: 0, + forceDiceResult: 50 + }; + const content = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.hbs', rollData); + const dialog = new RdDRoll(a, rollData, content, {classes:['rdd-roll-dialog'], width:650, height:'fit-content'}, { + name: 'comp-res', label: 'Test Result', + callbacks: [{action: r => { r.rolled = {isSuccess: true, code: 'particuliere'}; }}] + }); + await dialog.render(true); + await new Promise(r => setTimeout(r, 600)); + const btn = dialog.element?.querySelector('button[data-action=onAction]'); + if (btn) btn.click(); + await new Promise(r => setTimeout(r, 2000)); + const msgs = game.messages.contents.slice(-2); + const found = msgs.some(m => (m.content || '').length > 50); + return { chatHasResult: found, msgCount: msgs.length }; + } catch(e) { return {error: e?.message || String(e)}; } +") +CHATOK=$(eval_ok "$R" chatHasResult) +ERR=$(eval_ok "$R" error) +[ "$CHATOK" = "True" ] && pass "TC-08-011: message chat résultat roll" || fail "TC-08-011: KO ($ERR)" +close_roll_dialogs + +# ============================================================================ +echo "" +echo "--- Cleanup ---" +CLEANUP=$(jse_get_async " + const a = game.actors.getName('TC-08 Test'); + if (a) { await a.delete(); return 'deleted'; } + return 'none'; +" | tr -d '"\n') +[ "$CLEANUP" = "deleted" ] && pass "Cleanup: actor deleted" || fail "Cleanup: $CLEANUP" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-09-roll-v2.sh b/tests/scripts/tc-09-roll-v2.sh new file mode 100755 index 00000000..d8116a26 --- /dev/null +++ b/tests/scripts/tc-09-roll-v2.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-09 — Système de Jet V2 (Roll Dialog) +# Usage: ./tc-09-roll-v2.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-09 — Système de Jet V2 (Roll Dialog) ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# Setup +SETUP=$(jse_get_async " + const ex = game.actors.getName('TC-09 Test'); + if (ex) await ex.delete(); + const [actor] = await Actor.createDocuments([{ + type: 'personnage', name: 'TC-09 Test', + system: { carac: { force: {value: 12} }, sante: { vie: {value: 12, max: 12}, endurance: {value: 20, max: 20} } } + }]); + const [comp] = await actor.createEmbeddedDocuments('Item', [{ + type: 'competence', name: 'TC-09 Comp', + system: { categorie: 'physique', niveau: 5, xp: 0, base: 0, carac: 'force' } + }]); + return { actorId: actor.id, compId: comp?.id }; +") +AID=$(eval_ok "$SETUP" actorId) + +# ============================================================================ +echo "--- TC-09-001: Toggle V2 activé ---" +# ============================================================================ +R=$(jse_get_async " + try { + const SETTING = 'rdd-advanced-roll-dialog-v2'; + const before = game.settings.get('foundryvtt-reve-de-dragon', SETTING); + await game.settings.set('foundryvtt-reve-de-dragon', SETTING, true); + const after = game.settings.get('foundryvtt-reve-de-dragon', SETTING); + return { before, after, ok: after === true }; + } catch(e) { return {error: e.message}; } +") +OK=$(eval_ok "$R" ok) +ERR=$(eval_ok "$R" error) +[ "$OK" = "True" ] && pass "TC-09-001: ROLL_DIALOG_V2 activé" || fail "TC-09-001: KO ($ERR)" + +# ============================================================================ +echo "--- TC-09-002: Roll V2 — module chargé ---" +# ============================================================================ +R=$(jse_get_async " + try { + const mod = await import('/systems/foundryvtt-reve-de-dragon/module/roll/roll-dialog.mjs'); + const RollDialog = mod.default ?? mod.RollDialog; + return { ok: !!RollDialog, hasCreate: typeof RollDialog?.create === 'function' }; + } catch(e) { return {error: e.message}; } +") +OK=$(eval_ok "$R" ok) +ERR=$(eval_ok "$R" error) +[ "$OK" = "True" ] && pass "TC-09-002: RollDialog V2 module chargé" || fail "TC-09-002: KO ($ERR)" + +# ============================================================================ +echo "--- TC-09-005: Toggle V2 désactivé ---" +# ============================================================================ +R=$(jse_get_async " + try { + const SETTING = 'rdd-advanced-roll-dialog-v2'; + await game.settings.set('foundryvtt-reve-de-dragon', SETTING, false); + const after = game.settings.get('foundryvtt-reve-de-dragon', SETTING); + return { after, ok: after === false }; + } catch(e) { return {error: e.message}; } +") +OK=$(eval_ok "$R" ok) +ERR=$(eval_ok "$R" error) +[ "$OK" = "True" ] && pass "TC-09-005: ROLL_DIALOG_V2 désactivé" || fail "TC-09-005: KO ($ERR)" + +# Cleanup +jse_get_async " + const a = game.actors.getName('TC-09 Test'); + if (a) await a.delete(); + return 'ok'; +" >/dev/null 2>&1 + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-10-des-custom.sh b/tests/scripts/tc-10-des-custom.sh new file mode 100755 index 00000000..21470450 --- /dev/null +++ b/tests/scripts/tc-10-des-custom.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-10 — Dés Custom +# Usage: ./tc-10-des-custom.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-10 — Dés Custom ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-10-001: Dé TMR (d8) ---" +# ============================================================================ +R=$(jse_get_async " + try { + const dice = game.system.rdd.Dice; + if (!dice) return {error: 'no Dice'}; + const roll = await dice.roll('1dt'); + const total = roll.total; + const valid = total >= 1 && total <= 8; + return { total, valid, formula: roll.formula }; + } catch(e) { return {error: e?.message || String(e)}; } +") +TOTAL=$(eval_ok "$R" total) +VALID=$(eval_ok "$R" valid) +ERR=$(eval_ok "$R" error) +[ "$VALID" = "True" ] && pass "TC-10-001: dé TMR = $TOTAL (1-8)" || fail "TC-10-001: KO ($ERR)" + +# ============================================================================ +echo "--- TC-10-002: Dé Draconique (d8 explosif) ---" +# ============================================================================ +R=$(jse_get_async " + try { + const dice = game.system.rdd.Dice; + const roll = await dice.roll('1dd'); + const total = roll.total; + // draconic die can explode on 7, total can be 0 on failure + const valid = total >= 0; + return { total, valid, formula: roll.formula }; + } catch(e) { return {error: e?.message || String(e)}; } +") +TOTAL=$(eval_ok "$R" total) +VALID=$(eval_ok "$R" valid) +ERR=$(eval_ok "$R" error) +[ "$VALID" = "True" ] && pass "TC-10-002: dé draconique = $TOTAL" || fail "TC-10-002: KO ($ERR)" + +# ============================================================================ +echo "--- TC-10-003: Dé Heure (d12) ---" +# ============================================================================ +R=$(jse_get_async " + try { + const dice = game.system.rdd.Dice; + if (typeof dice.rollHeure !== 'function') return {error: 'no rollHeure'}; + const result = await dice.rollHeure(); + // rollHeure returns a Number directly, not a Roll object + const total = typeof result === 'number' ? result : result?.total; + const valid = total >= 0 && total <= 11; + return { total, valid, type: typeof result }; + } catch(e) { return {error: e?.message || String(e)}; } +") +TOTAL=$(eval_ok "$R" total) +VALID=$(eval_ok "$R" valid) +ERR=$(eval_ok "$R" error) +[ "$VALID" = "True" ] && pass "TC-10-003: dé heure = $TOTAL (0-11)" || fail "TC-10-003: KO ($ERR)" + +# ============================================================================ +echo "--- TC-10-005: Résolution d100 ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { RdDResolutionTable } = await import('/systems/foundryvtt-reve-de-dragon/module/rdd-resolution-table.js'); + const result = await RdDResolutionTable.rollData({carac: 12, level: 0, forceDiceResult: 50}); + const code = result?.rolled?.code; + const isSuccess = result?.rolled?.isSuccess; + return { code, isSuccess, hasRolled: !!result?.rolled }; + } catch(e) { return {error: e?.message || String(e)}; } +") +CODE=$(eval_ok "$R" code) +HASROLLED=$(eval_ok "$R" hasRolled) +ERR=$(eval_ok "$R" error) +[ "$HASROLLED" = "True" ] && pass "TC-10-005: résolution d100 code=$CODE" || fail "TC-10-005: KO ($ERR)" + +# ============================================================================ +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-11-combat.sh b/tests/scripts/tc-11-combat.sh new file mode 100755 index 00000000..59fa68f1 --- /dev/null +++ b/tests/scripts/tc-11-combat.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-11 — Combat +# Usage: ./tc-11-combat.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-11 — Combat ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# Setup +SETUP=$(jse_get_async " + const ex = game.actors.getName('TC-11 Test'); + if (ex) await ex.delete(); + const [actor] = await Actor.createDocuments([{ + type: 'personnage', name: 'TC-11 Test', + system: { carac: { force: {value: 12} }, sante: { vie: {value: 12, max: 12}, endurance: {value: 20, max: 20} } } + }]); + const [arme] = await actor.createEmbeddedDocuments('Item', [{ + type: 'arme', name: 'TC-11 Epee', + system: { categorie: 'cac', degats: '3', competence: 'Mêlée', competence2: '', encombrement: 2, mains: 1 } + }]); + return { actorId: actor.id, armeId: arme?.id }; +") +AID=$(eval_ok "$SETUP" actorId) + +# ============================================================================ +echo "--- TC-11-001: Création combat ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { RdDCombat } = await import('/systems/foundryvtt-reve-de-dragon/module/combat/rdd-combat.js'); + return { hasCombat: !!RdDCombat, hasCreate: typeof RdDCombat.create === 'function' }; + } catch(e) { return {error: e.message}; } +") +HASC=$(eval_ok "$R" hasCombat) +ERR=$(eval_ok "$R" error) +[ "$HASC" = "True" ] && pass "TC-11-001: RdDCombat chargé" || fail "TC-11-001: KO ($ERR)" + +# ============================================================================ +echo "--- TC-11-002: Initiative — phases ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { RdDInitiative } = await import('/systems/foundryvtt-reve-de-dragon/module/combat/initiative.mjs'); + return { hasInit: !!RdDInitiative, hasRoll: typeof RdDInitiative?.roll === 'function' }; + } catch(e) { return {error: e.message}; } +") +HASINIT=$(eval_ok "$R" hasInit) +ERR=$(eval_ok "$R" error) +[ "$HASINIT" = "True" ] && pass "TC-11-002: RdDInitiative chargé" || fail "TC-11-002: KO ($ERR)" + +# Cleanup +jse_get_async "const a = game.actors.getName('TC-11 Test'); if (a) await a.delete(); return 'ok';" >/dev/null 2>&1 + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-12-calendrier.sh b/tests/scripts/tc-12-calendrier.sh new file mode 100755 index 00000000..2c9b045b --- /dev/null +++ b/tests/scripts/tc-12-calendrier.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-12 — Calendrier / Temps +# Usage: ./tc-12-calendrier.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-12 — Calendrier / Temps ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-12-001: Calendrier visible ---" +# ============================================================================ +R=$(jse_get_async " + try { + const cal = game.system.rdd?.calendrier; + if (!cal) return {error: 'no calendrier'}; + const el = document.querySelector('.calendar, #rdd-calendar, [class*=calendar]'); + const visible = el !== null && el.offsetParent !== null; + return { exists: !!cal, visible, tag: el?.tagName }; + } catch(e) { return {error: e?.message || String(e)}; } +") +EXISTS=$(eval_ok "$R" exists) +ERR=$(eval_ok "$R" error) +[ "$EXISTS" = "True" ] && pass "TC-12-001: calendrier existe" || fail "TC-12-001: KO ($ERR)" + +# ============================================================================ +echo "--- TC-12-002: Heure Draconique ---" +# ============================================================================ +R=$(jse_get_async " + try { + const cal = game.system.rdd?.calendrier; + if (!cal) return {error: 'no calendrier'}; + const timestamp = cal.timestamp || cal.getTimestamp?.(); + const heure = cal.dateCourante?.()?.getHeure?.() ?? cal.getCurrentHeure?.(); + const date = cal.dateCourante?.(); + return { heure, hasDate: !!date, timestamp }; + } catch(e) { return {error: e?.message || String(e)}; } +") +HEURE=$(eval_ok "$R" heure) +HASDATE=$(eval_ok "$R" hasDate) +ERR=$(eval_ok "$R" error) +[ "$HASDATE" = "True" ] && pass "TC-12-002: heure draconique = $HEURE" || fail "TC-12-002: KO ($ERR)" + +# ============================================================================ +echo "--- TC-12-003: Avancement du temps ---" +# ============================================================================ +R=$(jse_get_async " + try { + const cal = game.system.rdd?.calendrier; + if (!cal) return {error: 'no calendrier'}; + const ts = cal.timestamp; + const before = ts.indexDate + ':' + ts.indexMinute; + const newTs = ts.addJours(1); + if (cal.setNewTimestamp) await cal.setNewTimestamp(newTs); + else if (cal.incrementerJour) await cal.incrementerJour(); + await new Promise(r => setTimeout(r, 500)); + const after = cal.timestamp.indexDate + ':' + cal.timestamp.indexMinute; + const changed = before !== after; + return { before, after, changed }; + } catch(e) { return {error: e?.message || String(e)}; } +") +CHANGED=$(eval_ok "$R" changed) +ERR=$(eval_ok "$R" error) +[ "$CHANGED" = "True" ] && pass "TC-12-003: temps avance" || fail "TC-12-003: KO ($ERR)" + +# ============================================================================ +echo "--- TC-12-004: Nombres Astraux ---" +# ============================================================================ +R=$(jse_get_async " + try { + const cal = game.system.rdd?.calendrier; + if (!cal) return {error: 'no calendrier'}; + const na = cal.getNombresAstraux?.() ?? cal.nombresAstraux; + return { hasNA: !!na, na: typeof na === 'object' ? JSON.stringify(na).substring(0, 200) : na }; + } catch(e) { return {error: e?.message || String(e)}; } +") +HASNA=$(eval_ok "$R" hasNA) +ERR=$(eval_ok "$R" error) +[ "$HASNA" = "True" ] && pass "TC-12-004: nombres astraux disponibles" || fail "TC-12-004: KO ($ERR)" + +# ============================================================================ +echo "--- TC-12-007: Chronologie ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { DialogChronologie } = await import('/systems/foundryvtt-reve-de-dragon/module/dialog-chronologie.js'); + if (!DialogChronologie) return {error: 'no DialogChronologie'}; + await DialogChronologie.create(); + await new Promise(r => setTimeout(r, 2000)); + const el = document.querySelector('.dialog-chronologie, [class*=chronolog]'); + const ok = !!el; + // Close it + const apps = Object.values(ui.windows).filter(w => w.constructor?.name?.includes('Chronolog')); + for (const a of apps) await a.close(); + if (!apps.length && el) el.remove(); + return { opened: ok }; + } catch(e) { return {error: e?.message || String(e)}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-12-007: chronologie ouverte" || fail "TC-12-007: KO ($ERR)" + +# ============================================================================ +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-13-sommeil.sh b/tests/scripts/tc-13-sommeil.sh new file mode 100755 index 00000000..697c84cf --- /dev/null +++ b/tests/scripts/tc-13-sommeil.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-13 — Sommeil / Repos +# Usage: ./tc-13-sommeil.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-13 — Sommeil / Repos ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# Setup +SETUP=$(jse_get_async " + const ex = game.actors.getName('TC-13 Test'); + if (ex) await ex.delete(); + const [actor] = await Actor.createDocuments([{ + type: 'personnage', name: 'TC-13 Test', + system: { sante: { vie: {value: 12, max: 12}, endurance: {value: 20, max: 20} } } + }]); + return { actorId: actor.id }; +") +AID=$(eval_ok "$SETUP" actorId) + +# ============================================================================ +echo "--- TC-13-001: Repos (ancien) /sommeil ---" +# ============================================================================ +R=$(jse_get_async " + try { + game.system.rdd.commands.onChatMessage('/sommeil', {content: '/sommeil', speaker: {alias: 'TC-13'}}); + await new Promise(r => setTimeout(r, 2000)); + const el = document.querySelector('[class*=chateau-dormant], [class*=sommeil], .dialog-chateau-dormant'); + return { opened: !!el }; + } catch(e) { return {error: e.message}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-13-001: dialog sommeil ouvert" || fail "TC-13-001: KO ($ERR)" +jse_get_async " + const apps = Object.values(ui.windows).filter(w => w.constructor?.name?.includes('Chateau') || w.constructor?.name?.includes('Dormant') || w.constructor?.name?.includes('Sommeil')); + for (const a of apps) await a.close(); + return 'closed'; +" >/dev/null 2>&1 + +# ============================================================================ +echo "--- TC-13-005: Distribution Stress GM /stress ---" +# ============================================================================ +R=$(jse_get_async " + try { + game.system.rdd.commands.onChatMessage('/stress', {content: '/stress', speaker: {alias: 'TC-13'}}); + await new Promise(r => setTimeout(r, 2000)); + const el = document.querySelector('[class*=dialog-stress], [class*=stress]'); + return { opened: !!el }; + } catch(e) { return {error: e.message}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-13-005: dialog stress ouvert" || fail "TC-13-005: KO ($ERR)" +jse_get_async " + const apps = Object.values(ui.windows).filter(w => w.constructor?.name?.includes('Stress')); + for (const a of apps) await a.close(); + return 'closed'; +" >/dev/null 2>&1 + +# Cleanup +jse_get_async "const a = game.actors.getName('TC-13 Test'); if (a) await a.delete(); return 'ok';" >/dev/null 2>&1 + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-14-voyage.sh b/tests/scripts/tc-14-voyage.sh new file mode 100755 index 00000000..caf3068a --- /dev/null +++ b/tests/scripts/tc-14-voyage.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-14 — Voyage +# Usage: ./tc-14-voyage.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-14 — Voyage ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# Setup +SETUP=$(jse_get_async " + const ex = game.actors.getName('TC-14 Test'); + if (ex) await ex.delete(); + const [actor] = await Actor.createDocuments([{type:'personnage', name:'TC-14 Test'}]); + return { actorId: actor.id }; +") +AID=$(eval_ok "$SETUP" actorId) + +# ============================================================================ +echo "--- TC-14-001: Dialogue Voyage ---" +# ============================================================================ +R=$(jse_get_async " + try { + game.system.rdd.commands.onChatMessage('/voyage', {content: '/voyage', speaker: {alias: 'TC-14'}}); + await new Promise(r => setTimeout(r, 2000)); + const el = document.querySelector('[class*=fatigue-voyage], [class*=voyage], .dialog-fatigue-voyage'); + return { opened: !!el }; + } catch(e) { return {error: e.message}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-14-001: dialog voyage ouvert" || fail "TC-14-001: KO ($ERR)" +# Close +jse_get_async " + const apps = Object.values(ui.windows).filter(w => w.constructor?.name?.includes('Voyage') || w.constructor?.name?.includes('Fatigue')); + for (const a of apps) await a.close(); + return 'closed'; +" >/dev/null 2>&1 + +# Cleanup +jse_get_async "const a = game.actors.getName('TC-14 Test'); if (a) await a.delete(); return 'ok';" >/dev/null 2>&1 + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-15-tmr.sh b/tests/scripts/tc-15-tmr.sh new file mode 100755 index 00000000..f3bfce45 --- /dev/null +++ b/tests/scripts/tc-15-tmr.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-15 — TMR (Terres Médianes du Rêve) +# Usage: ./tc-15-tmr.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-15 — TMR (Terres Médianes du Rêve) ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# Setup: créer un acteur HR (haut rêvant) +SETUP=$(jse_get_async " + const ex = game.actors.getName('TC-15 Test'); + if (ex) await ex.delete(); + const [actor] = await Actor.createDocuments([{ + type: 'personnage', name: 'TC-15 Test', + system: { + carac: { force: {value: 12}, reve: {value: 12} }, + reve: { reve: {value: 12}, tmrpos: {coord: 'A1'} }, + sante: { vie: {value: 12, max: 12}, endurance: {value: 20, max: 20} } + } + }]); + return { actorId: actor.id }; +") +AID=$(eval_ok "$SETUP" actorId) + +# ============================================================================ +echo "--- TC-15-002: Génération rencontre TMR ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + // Check TMRRencontres exists + const { TMRRencontres } = await import('/systems/foundryvtt-reve-de-dragon/module/tmr-rencontres.js'); + const hasRencontres = !!TMRRencontres; + return { hasRencontres }; + } catch(e) { return {error: e?.message || String(e)}; } +") +HASREN=$(eval_ok "$R" hasRencontres) +ERR=$(eval_ok "$R" error) +[ "$HASREN" = "True" ] && pass "TC-15-002: TMRRencontres module chargé" || fail "TC-15-002: KO ($ERR)" + +# ============================================================================ +echo "--- TC-15-003: Recherche TMR ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { TMRUtility } = await import('/systems/foundryvtt-reve-de-dragon/module/tmr-utility.js'); + const tmr = TMRUtility.getTMR('A1'); + return { found: !!tmr, coord: tmr?.coord, type: tmr?.type, label: tmr?.label }; + } catch(e) { return {error: e?.message || String(e)}; } +") +FOUND=$(eval_ok "$R" found) +COORD=$(eval_ok "$R" coord) +ERR=$(eval_ok "$R" error) +[ "$FOUND" = "True" ] && pass "TC-15-003: TMR A1 = $COORD" || fail "TC-15-003: KO ($ERR)" + +# ============================================================================ +echo "--- TC-15-004: TMR Aléatoire ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { TMRUtility } = await import('/systems/foundryvtt-reve-de-dragon/module/tmr-utility.js'); + const tmr = TMRUtility.getTMRAleatoire?.() ?? TMRUtility.randomTMR?.(); + return { found: !!tmr, coord: tmr?.coord, type: tmr?.type }; + } catch(e) { return {error: e?.message || String(e)}; } +") +FOUND=$(eval_ok "$R" found) +COORD=$(eval_ok "$R" coord) +ERR=$(eval_ok "$R" error) +[ "$FOUND" = "True" ] && pass "TC-15-004: TMR aléatoire = $COORD" || fail "TC-15-004: KO ($ERR)" + +# ============================================================================ +echo "--- TC-15-006: Coordonnées TMR acteur ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + await a.update({'system.reve.tmrpos.coord': 'M5'}); + await new Promise(r => setTimeout(r, 500)); + const coord = a.system.reve?.tmrpos?.coord; + return { coord, ok: coord === 'M5' }; + } catch(e) { return {error: e?.message || String(e)}; } +") +COORD=$(eval_ok "$R" coord) +OK=$(eval_ok "$R" ok) +ERR=$(eval_ok "$R" error) +[ "$OK" = "True" ] && pass "TC-15-006: coord TMR acteur = $COORD" || fail "TC-15-006: KO ($ERR)" + +# ============================================================================ +echo "--- TC-15-007: Réinsertion aléatoire ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + if (typeof a.reinsertionAleatoire !== 'function') return {error: 'no reinsertionAleatoire'}; + await a.reinsertionAleatoire(); + await new Promise(r => setTimeout(r, 500)); + const coord = a.system.reve?.tmrpos?.coord; + return { newCoord: coord, changed: coord !== 'M5' }; + } catch(e) { return {error: e?.message || String(e)}; } +") +NEWC=$(eval_ok "$R" newCoord) +CHANGED=$(eval_ok "$R" changed) +ERR=$(eval_ok "$R" error) +[ "$CHANGED" = "True" ] && pass "TC-15-007: réinsertion = $NEWC" || fail "TC-15-007: KO ($ERR)" + +# ============================================================================ +echo "" +echo "--- Cleanup ---" +CLEANUP=$(jse_get_async " + const a = game.actors.getName('TC-15 Test'); + if (a) { await a.delete(); return 'deleted'; } + return 'none'; +" | tr -d '"\n') +[ "$CLEANUP" = "deleted" ] && pass "Cleanup: actor deleted" || fail "Cleanup: $CLEANUP" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-16-commandes-chat.sh b/tests/scripts/tc-16-commandes-chat.sh new file mode 100755 index 00000000..976cab88 --- /dev/null +++ b/tests/scripts/tc-16-commandes-chat.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-16 — Commandes Chat +# Usage: ./tc-16-commandes-chat.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-16 — Commandes Chat ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# Helper: run a chat command and check for a chat message +test_chat_cmd() { + local id="$1" cmd="$2" pattern="$3" + local before after + before=$(jse_get_async "return game.messages.size;" | tr -d '"\n') + jse_get_async " + const msg = {content: '$cmd', speaker: {alias: 'TC-16'}}; + game.system.rdd.commands.onChatMessage('$cmd', msg); + await new Promise(r => setTimeout(r, 1500)); + return 'done'; + " >/dev/null 2>&1 + after=$(jse_get_async "return game.messages.size;" | tr -d '"\n') + local newcount=$((after - before)) + local match + match=$(jse_get_async " + const msgs = game.messages.contents.slice(-$newcount); + const found = msgs.some(m => (m.content || '').match(/$pattern/)); + return found; + " | tr -d '"\n') + [ "$match" = "true" ] && pass "$id: $cmd → match '$pattern'" || fail "$id: $cmd → no match (new=$newcount)" +} + +# TC-16-001: /aide +echo "--- TC-16-001: /aide ---" +test_chat_cmd "TC-16-001" "/aide" "aide|help|command" + +# TC-16-003: /table queue +echo "--- TC-16-003: /table queue ---" +test_chat_cmd "TC-16-003" "/table queue" "Queue|queue|dragon" + +# TC-16-004: /table ombre +echo "--- TC-16-004: /table ombre ---" +test_chat_cmd "TC-16-004" "/table ombre" "Ombre|ombre|Thanatos" + +# TC-16-005: /table tete +echo "--- TC-16-005: /table tete ---" +test_chat_cmd "TC-16-005" "/table tete" "[Tt]ête|Tete|Dragon|tirage" + +# TC-16-006: /table souffle +echo "--- TC-16-006: /table souffle ---" +test_chat_cmd "TC-16-006" "/table souffle" "Souffle|souffle" + +# TC-16-007: /table tarot +echo "--- TC-16-007: /table tarot ---" +test_chat_cmd "TC-16-007" "/table tarot" "[Tt]arot" + +# TC-16-017: /meteo +echo "--- TC-16-017: /meteo ---" +test_chat_cmd "TC-16-017" "/meteo" "[Mm]étéo|[Vv]ent|[Mm]er|[Nn]uage|[Pp]luie" + +# TC-16-018: /nom +echo "--- TC-16-018: /nom ---" +test_chat_cmd "TC-16-018" "/nom" "nom|Nom|proposition|Créer" + +# TC-16-023: /ddr +echo "--- TC-16-023: /ddr ---" +test_chat_cmd "TC-16-023" "/ddr" "draconique|Dé|d[ée]|[Dd]ragon" + +# TC-16-029: /chrono +echo "--- TC-16-029: /chrono ---" +R=$(jse_get_async " + try { + game.system.rdd.commands.onChatMessage('/chrono', {content: '/chrono', speaker: {alias: 'TC-16'}}); + await new Promise(r => setTimeout(r, 2000)); + const el = document.querySelector('.dialog-chronologie, [class*=chronolog]'); + return { opened: !!el }; + } catch(e) { return {error: e.message}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-16-029: /chrono dialog ouvert" || fail "TC-16-029: KO ($ERR)" +# Close chrono +jse_get_async " + const apps = Object.values(ui.windows).filter(w => w.constructor?.name?.includes('Chronolog')); + for (const a of apps) await a.close(); + return 'closed'; +" >/dev/null 2>&1 + +# ============================================================================ +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 10 ] 2>/dev/null && pass "Console errors ≤10 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-17-token-hud.sh b/tests/scripts/tc-17-token-hud.sh new file mode 100755 index 00000000..498c21ac --- /dev/null +++ b/tests/scripts/tc-17-token-hud.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-17 — Token HUD +# Usage: ./tc-17-token-hud.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-17 — Token HUD ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-17-006: HUD token non-combat (pas d'erreur) ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { RdDTokenHud } = await import('/systems/foundryvtt-reve-de-dragon/module/rdd-token-hud.js'); + if (!RdDTokenHud) return {error: 'no RdDTokenHud'}; + const hasInit = typeof RdDTokenHud.init === 'function'; + const hasOnRender = typeof RdDTokenHud.onRenderTokenHUD === 'function'; + return { hasInit, hasOnRender, ok: hasInit || hasOnRender }; + } catch(e) { return {error: e.message}; } +") +OK=$(eval_ok "$R" ok) +ERR=$(eval_ok "$R" error) +[ "$OK" = "True" ] && pass "TC-17-006: RdDTokenHud chargé" || fail "TC-17-006: KO ($ERR)" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-18-dialogues.sh b/tests/scripts/tc-18-dialogues.sh new file mode 100755 index 00000000..fd1df62c --- /dev/null +++ b/tests/scripts/tc-18-dialogues.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-18 — Dialogues +# Usage: ./tc-18-dialogues.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-18 — Dialogues ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# Setup +SETUP=$(jse_get_async " + const ex = game.actors.getName('TC-18 Test'); + if (ex) await ex.delete(); + const [actor] = await Actor.createDocuments([{type:'personnage', name:'TC-18 Test'}]); + return { actorId: actor.id }; +") +AID=$(eval_ok "$SETUP" actorId) + +# ============================================================================ +echo "--- TC-18-014: Dialogues V2 — window positioning ---" +# ============================================================================ +R=$(jse_get_async " + try { + // Open a DialogV2 and check it has window controls (position, resize) + const dlg = new foundry.applications.api.DialogV2({ + title: 'TC-18 Test Dialog', + content: '
Test
', + buttons: [{action: 'close', label: 'Fermer', callback: (e,b) => b.close()}] + }); + await dlg.render(true); + await new Promise(r => setTimeout(r, 1000)); + const el = dlg.element; + const hasWindow = !!el?.querySelector('.window-header'); + const hasControls = !!el?.querySelector('.window-controls, .close, .header-button'); + await dlg.close(); + return { hasWindow, hasControls }; + } catch(e) { return {error: e.message}; } +") +HASWIN=$(eval_ok "$R" hasWindow) +ERR=$(eval_ok "$R" error) +[ "$HASWIN" = "True" ] && pass "TC-18-014: dialog V2 avec window header" || fail "TC-18-014: KO ($ERR)" + +# ============================================================================ +echo "--- TC-18-015: Dialogues V2 — boutons (au moins 1 bouton) ---" +# ============================================================================ +R=$(jse_get_async " + try { + const dlg = new foundry.applications.api.DialogV2({ + title: 'TC-18 Test Buttons', + content: 'Test
', + buttons: [{action: 'ok', label: 'OK', callback: (e,b) => b.close()}] + }); + await dlg.render(true); + await new Promise(r => setTimeout(r, 1000)); + const btns = dlg.element?.querySelectorAll('button'); + const count = btns?.length ?? 0; + await dlg.close(); + return { btnCount: count, ok: count >= 1 }; + } catch(e) { return {error: e.message}; } +") +OK=$(eval_ok "$R" ok) +ERR=$(eval_ok "$R" error) +[ "$OK" = "True" ] && pass "TC-18-015: dialog V2 a ≥1 bouton" || fail "TC-18-015: KO ($ERR)" + +# Cleanup +jse_get_async "const a = game.actors.getName('TC-18 Test'); if (a) await a.delete(); return 'ok';" >/dev/null 2>&1 + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-19-tirage.sh b/tests/scripts/tc-19-tirage.sh new file mode 100755 index 00000000..35999b34 --- /dev/null +++ b/tests/scripts/tc-19-tirage.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-19 — Tirage (FenetreRechercheTirage) +# Usage: ./tc-19-tirage.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-19 — Tirage (FenetreRechercheTirage) ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-19-001: Ouverture /tirage ---" +# ============================================================================ +R=$(jse_get_async " + try { + game.system.rdd.commands.onChatMessage('/tirage', {content: '/tirage', speaker: {alias: 'TC-19'}}); + await new Promise(r => setTimeout(r, 2000)); + const el = document.querySelector('[class*=tirage], [class*=recherche-tirage], .fenetre-recherche-tirage'); + return { opened: !!el }; + } catch(e) { return {error: e.message}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-19-001: fenêtre tirage ouverte" || fail "TC-19-001: KO ($ERR)" + +# Close +jse_get_async " + const apps = Object.values(ui.windows).filter(w => w.constructor?.name?.includes('Tirage') || w.constructor?.name?.includes('Recherche')); + for (const a of apps) await a.close(); + return 'closed'; +" >/dev/null 2>&1 + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-20-gen-aleatoire.sh b/tests/scripts/tc-20-gen-aleatoire.sh new file mode 100755 index 00000000..f31ac4dc --- /dev/null +++ b/tests/scripts/tc-20-gen-aleatoire.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-20 — Génération Aléatoire de Personnage +# Usage: ./tc-20-gen-aleatoire.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-20 — Génération Aléatoire de Personnage ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-20-003: Génération nom ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { RdDNameGen } = await import('/systems/foundryvtt-reve-de-dragon/module/rdd-namegen.js'); + if (!RdDNameGen) return {error: 'no RdDNameGen'}; + const name = RdDNameGen.generate?.() ?? await RdDNameGen.generate(); + return { hasName: !!name, name: String(name).substring(0, 50) }; + } catch(e) { return {error: e.message}; } +") +HASNAME=$(eval_ok "$R" hasName) +NAME=$(eval_ok "$R" name) +ERR=$(eval_ok "$R" error) +[ "$HASNAME" = "True" ] && pass "TC-20-003: nom généré = $NAME" || fail "TC-20-003: KO ($ERR)" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-22-compendiums.sh b/tests/scripts/tc-22-compendiums.sh new file mode 100755 index 00000000..cc411706 --- /dev/null +++ b/tests/scripts/tc-22-compendiums.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-22 — Compendiums +# Usage: ./tc-22-compendiums.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-22 — Compendiums ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-22-001: Packs chargés ---" +# ============================================================================ +R=$(jse_get_async " + try { + const packs = game.packs.filter(p => p.metadata?.system === 'foundryvtt-reve-de-dragon'); + return { count: packs.length, names: packs.map(p => p.metadata?.name).join(',') }; + } catch(e) { return {error: e.message}; } +") +COUNT=$(eval_ok "$R" count) +ERR=$(eval_ok "$R" error) +assert_gt "TC-22-001: ≥10 packs chargés ($COUNT)" "$COUNT" 9 + +# ============================================================================ +echo "--- TC-22-002: Compendium Compétences ---" +# ============================================================================ +R=$(jse_get_async " + try { + const packs = game.packs.filter(p => p.metadata?.system === 'foundryvtt-reve-de-dragon'); + const compPack = packs.find(p => p.metadata?.name?.includes('competence') || p.metadata?.label?.toLowerCase().includes('comp')); + if (!compPack) return {error: 'no competence pack', names: packs.map(p=>p.metadata.name).join(',')}; + const docs = await compPack.getDocuments(); + return { count: docs.length, valid: docs.length > 10 }; + } catch(e) { return {error: e.message}; } +") +CCOUNT=$(eval_ok "$R" count) +CVALID=$(eval_ok "$R" valid) +ERR=$(eval_ok "$R" error) +[ "$CVALID" = "True" ] && pass "TC-22-002: compétences pack = $CCOUNT items" || fail "TC-22-002: KO ($ERR)" + +# ============================================================================ +echo "--- TC-22-003: Compendium Sorts ---" +# ============================================================================ +R=$(jse_get_async " + try { + const packs = game.packs.filter(p => p.metadata?.system === 'foundryvtt-reve-de-dragon'); + const sortPack = packs.find(p => p.metadata?.name?.includes('sort') || p.metadata?.label?.toLowerCase().includes('sort')); + if (!sortPack) return {error: 'no sort pack'}; + const docs = await sortPack.getDocuments(); + return { count: docs.length, valid: docs.length > 20 }; + } catch(e) { return {error: e.message}; } +") +SCOUNT=$(eval_ok "$R" count) +SVALID=$(eval_ok "$R" valid) +ERR=$(eval_ok "$R" error) +[ "$SVALID" = "True" ] && pass "TC-22-003: sorts pack = $SCOUNT items" || fail "TC-22-003: KO ($ERR)" + +# ============================================================================ +echo "--- TC-22-004: Compendium Équipement ---" +# ============================================================================ +R=$(jse_get_async " + try { + const packs = game.packs.filter(p => p.metadata?.system === 'foundryvtt-reve-de-dragon'); + const eqPack = packs.find(p => p.metadata?.name?.includes('equipement') || p.metadata?.name?.includes('arme') || p.metadata?.label?.toLowerCase().includes('quip')); + if (!eqPack) return {error: 'no equipement pack'}; + const docs = await eqPack.getDocuments(); + return { count: docs.length, valid: docs.length > 5 }; + } catch(e) { return {error: e.message}; } +") +ECOUNT=$(eval_ok "$R" count) +EVALID=$(eval_ok "$R" valid) +ERR=$(eval_ok "$R" error) +[ "$EVALID" = "True" ] && pass "TC-22-004: équipement pack = $ECOUNT items" || fail "TC-22-004: KO ($ERR)" + +# ============================================================================ +echo "--- TC-22-006: Drag compendium → acteur ---" +# ============================================================================ +R=$(jse_get_async " + try { + const packs = game.packs.filter(p => p.metadata?.system === 'foundryvtt-reve-de-dragon'); + // Find an item pack + const itemPack = packs.find(p => p.documentName === 'Item'); + if (!itemPack) return {error: 'no item pack'}; + const docs = await itemPack.getDocuments(); + if (docs.length === 0) return {error: 'empty pack'}; + // Create a test actor + const [actor] = await Actor.createDocuments([{type:'personnage', name:'TC-22 Test'}]); + // Get first item from pack + const item = docs[0]; + // Clone it to actor + const [created] = await actor.createEmbeddedDocuments('Item', [item.toObject()]); + const has = actor.items.has(created.id); + await actor.delete(); + return { dragOk: has, itemName: item.name }; + } catch(e) { return {error: e.message}; } +") +DRAGOK=$(eval_ok "$R" dragOk) +ERR=$(eval_ok "$R" error) +[ "$DRAGOK" = "True" ] && pass "TC-22-006: drag compendium → acteur" || fail "TC-22-006: KO ($ERR)" + +# ============================================================================ +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-23-css-ui.sh b/tests/scripts/tc-23-css-ui.sh new file mode 100755 index 00000000..92017bbc --- /dev/null +++ b/tests/scripts/tc-23-css-ui.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-23 — CSS / UI / Layout +# Usage: ./tc-23-css-ui.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-23 — CSS / UI / Layout ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-23-001: Police CaslonAntique chargée ---" +# ============================================================================ +R=$(jse_get " + const cs = window.getComputedStyle(document.body); + const ff = cs.fontFamily || ''; + return { fontFamily: ff.substring(0, 200), hasFont: ff.length > 0 }; +") +HASFONT=$(eval_ok "$R" hasFont) +[ "$HASFONT" = "True" ] && pass "TC-23-001: police système chargée" || fail "TC-23-001: KO" + +# ============================================================================ +echo "--- TC-23-006: Sélecteurs natifs (pas de :input) ---" +# ============================================================================ +R=$(jse_get_async " + try { + // Check all system CSS files for :input selector + const sheets = Array.from(document.styleSheets); + let hasInputSelector = false; + for (const sheet of sheets) { + try { + const rules = sheet.cssRules; + for (const rule of rules) { + if (rule.selectorText && rule.selectorText.includes(':input')) { + hasInputSelector = true; + break; + } + } + } catch(e) {} // cross-origin + } + return { hasInputSelector, ok: !hasInputSelector }; + } catch(e) { return {error: e.message}; } +") +OK=$(eval_ok "$R" ok) +ERR=$(eval_ok "$R" error) +[ "$OK" = "True" ] && pass "TC-23-006: pas de sélecteur :input" || fail "TC-23-006: KO ($ERR)" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-25-hotbar.sh b/tests/scripts/tc-25-hotbar.sh new file mode 100755 index 00000000..f1d8c7df --- /dev/null +++ b/tests/scripts/tc-25-hotbar.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-25 — Hotbar +# Usage: ./tc-25-hotbar.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-25 — Hotbar ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-25-001: RdDHotbar module chargé ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { RdDHotbar } = await import('/systems/foundryvtt-reve-de-dragon/module/rdd-hotbar-drop.js'); + return { hasHotbar: !!RdDHotbar, hasOnDrop: typeof RdDHotbar?.onDropItem === 'function' || typeof RdDHotbar?.onHotbarDrop === 'function' }; + } catch(e) { return {error: e.message}; } +") +HASHB=$(eval_ok "$R" hasHotbar) +ERR=$(eval_ok "$R" error) +[ "$HASHB" = "True" ] && pass "TC-25-001: RdDHotbar chargé" || fail "TC-25-001: KO ($ERR)" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-26-possession.sh b/tests/scripts/tc-26-possession.sh new file mode 100755 index 00000000..8a2ee270 --- /dev/null +++ b/tests/scripts/tc-26-possession.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-26 — Possession V2 +# Usage: ./tc-26-possession.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-26 — Possession V2 ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-26-001: RdDPossession module chargé ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { RdDPossession } = await import('/systems/foundryvtt-reve-de-dragon/module/combat/rdd-possession.js'); + if (!RdDPossession) return {error: 'no RdDPossession'}; + const hasAttack = typeof RdDPossession.attack === 'function' || typeof RdDPossession.onAttaquePossession === 'function'; + return { hasAttack, ok: !!RdDPossession }; + } catch(e) { return {error: e.message}; } +") +OK=$(eval_ok "$R" ok) +ERR=$(eval_ok "$R" error) +[ "$OK" = "True" ] && pass "TC-26-001: RdDPossession chargé" || fail "TC-26-001: KO ($ERR)" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-27-active-effects.sh b/tests/scripts/tc-27-active-effects.sh new file mode 100755 index 00000000..ac6404e7 --- /dev/null +++ b/tests/scripts/tc-27-active-effects.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-27 — Active Effects +# Usage: ./tc-27-active-effects.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-27 — Active Effects ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# Setup +SETUP=$(jse_get_async " + const ex = game.actors.getName('TC-27 Test'); + if (ex) await ex.delete(); + const [actor] = await Actor.createDocuments([{ + type: 'personnage', name: 'TC-27 Test', + system: { + carac: { force: {value: 12} }, + sante: { vie: {value: 12, max: 12}, endurance: {value: 20, max: 20} } + } + }]); + return { actorId: actor.id }; +") +AID=$(eval_ok "$SETUP" actorId) + +# ============================================================================ +echo "--- TC-27-001: Création effet ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + const effect = await ActiveEffect.create({ + name: 'TC-27 Test Effect', + changes: [{ key: 'system.sante.vie.value', mode: CONST.ACTIVE_EFFECT_MODES.ADD, value: 5 }], + }, { parent: a }); + return { created: !!effect, id: effect?.id, name: effect?.name }; + } catch(e) { return {error: e.message}; } +") +CREATED=$(eval_ok "$R" created) +ERR=$(eval_ok "$R" error) +[ "$CREATED" = "True" ] && pass "TC-27-001: effet créé" || fail "TC-27-001: KO ($ERR)" + +# ============================================================================ +echo "--- TC-27-002: Application effet sur acteur ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + // Effect should have added 5 to vie.value — but ActiveEffect application + // might compute differently. Just check the effect exists and is active. + const hasEffect = a.effects.some(e => e.name === 'TC-27 Test Effect'); + const isActive = a.effects.some(e => e.name === 'TC-27 Test Effect' && !e.disabled); + const vie = a.system.sante?.vie?.value; + return { vie, hasEffect, isActive, applied: isActive }; + } catch(e) { return {error: e.message}; } +") +VIE=$(eval_ok "$R" vie) +APPLIED=$(eval_ok "$R" applied) +ERR=$(eval_ok "$R" error) +[ "$APPLIED" = "True" ] && pass "TC-27-002: vie = $VIE (effet appliqué)" || fail "TC-27-002: KO ($ERR)" + +# ============================================================================ +echo "--- TC-27-003: Suppression effet ---" +# ============================================================================ +R=$(jse_get_async " + try { + const a = game.actors.get('$AID'); + if (!a) return {error: 'no actor'}; + const effect = a.effects.find(e => e.name === 'TC-27 Test Effect'); + if (!effect) return {error: 'no effect'}; + await effect.delete(); + await new Promise(r => setTimeout(r, 500)); + const vieAfter = a.system.sante?.vie?.value; + const stillHas = a.effects.some(e => e.name === 'TC-27 Test Effect'); + return { vieAfter, stillHas, restored: !stillHas }; + } catch(e) { return {error: e.message}; } +") +RESTORED=$(eval_ok "$R" restored) +ERR=$(eval_ok "$R" error) +[ "$RESTORED" = "True" ] && pass "TC-27-003: effet supprimé, valeur restaurée" || fail "TC-27-003: KO ($ERR)" + +# ============================================================================ +echo "--- TC-27-004: Effets de statut ---" +# ============================================================================ +R=$(jse_get_async " + try { + const effects = game.rdd?.statusEffects ?? CONFIG.statusEffects; + return { count: effects?.length ?? 0, hasStatus: !!effects }; + } catch(e) { return {error: e.message}; } +") +COUNT=$(eval_ok "$R" count) +HASSTAT=$(eval_ok "$R" hasStatus) +ERR=$(eval_ok "$R" error) +[ "$HASSTAT" = "True" ] && pass "TC-27-004: statusEffects = $COUNT" || fail "TC-27-004: KO ($ERR)" + +# ============================================================================ +echo "" +echo "--- Cleanup ---" +CLEANUP=$(jse_get_async " + const a = game.actors.getName('TC-27 Test'); + if (a) { await a.delete(); return 'deleted'; } + return 'none'; +" | tr -d '"\n') +[ "$CLEANUP" = "deleted" ] && pass "Cleanup: actor deleted" || fail "Cleanup: $CLEANUP" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-28-journal.sh b/tests/scripts/tc-28-journal.sh new file mode 100755 index 00000000..29f2d4fc --- /dev/null +++ b/tests/scripts/tc-28-journal.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-28 — Journal Sheet +# Usage: ./tc-28-journal.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-28 — Journal Sheet ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-28-001: Journal système s'ouvre ---" +# ============================================================================ +R=$(jse_get_async " + try { + const journal = await JournalEntry.create({name: 'TC-28 Test', content: 'Test
'}, {temporary: true}); + if (!journal) return {error: 'create failed'}; + const sheet = journal.sheet; + await sheet.render(true); + await new Promise(r => setTimeout(r, 1500)); + const el = sheet.element; + const ok = !!el && document.body.contains(el); + await sheet.close(); + return { opened: ok, sheetName: sheet?.constructor?.name }; + } catch(e) { return {error: e.message}; } +") +OPENED=$(eval_ok "$R" opened) +ERR=$(eval_ok "$R" error) +[ "$OPENED" = "True" ] && pass "TC-28-001: journal sheet ouverte" || fail "TC-28-001: KO ($ERR)" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-29-coeur.sh b/tests/scripts/tc-29-coeur.sh new file mode 100755 index 00000000..2fba5a05 --- /dev/null +++ b/tests/scripts/tc-29-coeur.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-29 — Cœur (Attachment) +# Usage: ./tc-29-coeur.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-29 — Cœur (Attachment) ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-29-001: RdDCoeur module chargé ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { RdDCoeur } = await import('/systems/foundryvtt-reve-de-dragon/module/coeur/rdd-coeur.js'); + return { hasCoeur: !!RdDCoeur, hasCreate: typeof RdDCoeur?.create === 'function' }; + } catch(e) { return {error: e.message}; } +") +HASCOEUR=$(eval_ok "$R" hasCoeur) +ERR=$(eval_ok "$R" error) +[ "$HASCOEUR" = "True" ] && pass "TC-29-001: RdDCoeur chargé" || fail "TC-29-001: KO ($ERR)" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-30-appreciation.sh b/tests/scripts/tc-30-appreciation.sh new file mode 100755 index 00000000..1b30b959 --- /dev/null +++ b/tests/scripts/tc-30-appreciation.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-30 — Appréciation +# Usage: ./tc-30-appreciation.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-30 — Appréciation ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-30-001: Apprecier module chargé ---" +# ============================================================================ +R=$(jse_get_async " + try { + // Appreciation is not a separate module — check actor has appreciation methods + const a = game.actors.filter(a => a.type === 'personnage')[0]; + if (!a) return {error: 'no actor'}; + const hasAppreciation = typeof a.apprecier === 'function' || typeof a.getAppreciation === 'function' || a.items.some(i => i.system?.appreciation !== undefined); + return { hasApprecier: hasAppreciation }; + } catch(e) { return {error: e.message}; } +") +HAS=$(eval_ok "$R" hasApprecier) +ERR=$(eval_ok "$R" error) +[ "$HAS" = "True" ] && pass "TC-30-001: Apprecier chargé" || fail "TC-30-001: KO ($ERR)" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-31-meteo.sh b/tests/scripts/tc-31-meteo.sh new file mode 100755 index 00000000..7cda7c1c --- /dev/null +++ b/tests/scripts/tc-31-meteo.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# ============================================================================ +# TC-31 — Météo +# Usage: ./tc-31-meteo.sh [--navigate] +# ============================================================================ +set -uo pipefail +source "$(dirname "$0")/tc-header.sh" + +echo "" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ TC-31 — Météo ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo "" + +tc_ensure_ready + +# ============================================================================ +echo "--- TC-31-001: Génération météo ---" +# ============================================================================ +R=$(jse_get_async " + try { + const { RdDMeteo } = await import('/systems/foundryvtt-reve-de-dragon/module/rdd-meteo.js'); + if (!RdDMeteo) return {error: 'no RdDMeteo'}; + const meteo = RdDMeteo.getMeteo?.() ?? await RdDMeteo.getMeteo(); + return { hasMeteo: !!meteo, keys: meteo ? Object.keys(meteo).join(',') : '', hasVent: !!meteo?.vent }; + } catch(e) { return {error: e.message}; } +") +HASMETEO=$(eval_ok "$R" hasMeteo) +ERR=$(eval_ok "$R" error) +[ "$HASMETEO" = "True" ] && pass "TC-31-001: météo générée" || fail "TC-31-001: KO ($ERR)" + +# ============================================================================ +echo "--- TC-31-002: Affichage météo (/meteo) ---" +# ============================================================================ +BEFORE=$(jse_get_async "return game.messages.size;" | tr -d '"\n') +jse_get_async " + game.system.rdd.commands.onChatMessage('/meteo', {content: '/meteo', speaker: {alias: 'TC-31'}}); + await new Promise(r => setTimeout(r, 1500)); + return 'done'; +" >/dev/null 2>&1 +AFTER=$(jse_get_async "return game.messages.size;" | tr -d '"\n') +NEWMGS=$((AFTER - BEFORE)) +MATCH=$(jse_get_async " + const msgs = game.messages.contents.slice(-$NEWMGS); + const found = msgs.some(m => (m.content || '').match(/[Mm]étéo|[Vv]ent|[Mm]er/)); + return found; +" | tr -d '"\n') +[ "$MATCH" = "true" ] && pass "TC-31-002: /meteo message chat" || fail "TC-31-002: KO (new=$NEWMGS)" + +echo "" +echo "--- Console errors ---" +ERRS=$(tc_console_errors) +[ "$ERRS" -le 5 ] 2>/dev/null && pass "Console errors ≤5 ($ERRS)" || fail "Console errors: $ERRS" + +tc_summary \ No newline at end of file diff --git a/tests/scripts/tc-header.sh b/tests/scripts/tc-header.sh new file mode 100644 index 00000000..3c867728 --- /dev/null +++ b/tests/scripts/tc-header.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# ============================================================================ +# tc-header.sh — fonctions partagées pour tous les scripts TC +# Source: source "$(dirname "$0")/tc-header.sh" +# ============================================================================ +# Constants +URL="${URL:-https://localhost:31000}" +PASS="${PASS:-}" +WORLD="${WORLD:-Reve de Dragon}" +NAVIGATE="${1:-}" + +PASSED=0; FAILED=0 + +die() { echo " ✖ $*"; exit 1; } +pass() { echo " ✔ $1"; PASSED=$((PASSED+1)); } +fail() { echo " ✘ $1"; FAILED=$((FAILED+1)); } + +# Extract JSON value from evaluate_script output (```json\nVALUE\n```) +jse_get() { + chrome-devtools evaluate_script "() => { $1 }" 2>/dev/null \ + | sed -n '/^```json$/{n;p;}' +} + +jse_get_async() { + chrome-devtools evaluate_script "async () => { $1 }" 2>/dev/null \ + | sed -n '/^```json$/{n;p;}' +} + +# Async polling: single evaluate_script call, no bash loop +jse_wait_ready() { + local timeout_s="${1:-60}" poll_ms="${2:-1000}" + chrome-devtools evaluate_script "async () => { + const limit = $timeout_s * 1000 / $poll_ms; + for (let i = 0; i < limit; i++) { + if (typeof game !== 'undefined' && game.ready) return 'ready'; + await new Promise(r => setTimeout(r, $poll_ms)); + } + return 'timeout'; + }" 2>/dev/null | sed -n '/^```json$/{n;p;}' | tr -d '"\n' +} + +# Navigate + login if needed (--navigate flag) +# Env: PASS (admin password), PASS_USER (user password, empty by default) +PASS_USER="${PASS_USER:-}" +tc_login() { + [ -z "$PASS" ] && die "Admin password required: PASS=..." + echo "→ Navigating to $URL ..." + chrome-devtools new_page "$URL" --background false --timeout 15000 --isolatedContext "rdd-tests" >/dev/null 2>&1 + sleep 3 + local PW + PW=$(jse_get "window.location.pathname" | tr -d '"\n') + case "$PW" in + */join) + chrome-devtools evaluate_script "() => { + const sel = document.querySelector('select[name=userid]'); + if(sel) { + const opts = Array.from(sel.options); + const gm = opts.find(o => o.text === 'Gamemaster'); + const pick = gm || opts.find(o => o.value && !o.disabled) || opts.find(o => o.value); + if(pick) sel.value = pick.value; + } + const pw = document.querySelector('input[name=password]'); + if(pw) pw.value = '$PASS_USER'; + const btn = document.querySelector('button[name=join]'); + if(btn) btn.click(); + }" >/dev/null 2>&1 || true + ;; + */setup) + chrome-devtools evaluate_script "() => { + const links = document.querySelectorAll('[data-action=worldLaunch]'); + for(const l of links){ + const li = l.closest('li.package.world'); + if(li && li.querySelector('.package-title')?.textContent === '$WORLD'){ l.click(); break; } + } + }" >/dev/null 2>&1 || true + ;; + */auth) + chrome-devtools evaluate_script "() => { + const inp = document.querySelector('input[type=password]'); + if(inp) inp.value = '$PASS'; + const btn = document.querySelector('form button, button[type=submit]'); + if(btn) btn.click(); + }" >/dev/null 2>&1 || true + ;; + esac + echo "→ Waiting for game..." + jse_wait_ready 60 2 +} + +# Ensure game is ready, login if --navigate +tc_ensure_ready() { + local R + R=$(jse_wait_ready 15 2) + if [ "$R" != "ready" ] && [ "$NAVIGATE" = "--navigate" ]; then + R=$(tc_login) + fi + [ "$R" != "ready" ] && die "Game not ready" + echo " ✓ Game ready" + echo "" +} + +# Assert helpers +assert_eq() { local t="$1" a="$2" e="$3" + if [ "$a" = "$e" ]; then pass "$t"; else fail "$t (got '$a' expected '$e')"; fi +} +assert_gt() { local t="$1" a="$2" e="$3" + if [ "$a" -gt "$e" ] 2>/dev/null; then pass "$t"; else fail "$t ($a <= $e)"; fi +} +assert_contains() { local t="$1" a="$2" e="$3" + if echo "$a" | grep -q "$e"; then pass "$t"; else fail "$t (no '$e' in '$a')"; fi +} + +# Extract field from JSON result +eval_ok() { + local json="$1" k="$2" + echo "$json" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$k',''))" 2>/dev/null +} + +# Console error count +tc_console_errors() { + chrome-devtools list_console_messages --types error 2>/dev/null | grep -c '^msgid=' || true +} + +# Print summary +tc_summary() { + local TOTAL=$((PASSED + FAILED)) + echo "" + echo "╔══════════════════════════════════════════════════════════════╗" + printf "║ %2d / %2d passed, %2d failed ║\n" "$PASSED" "$TOTAL" "$FAILED" + echo "╚══════════════════════════════════════════════════════════════╝" + echo "" + [ "$FAILED" -gt 0 ] && exit 1 || exit 0 +} \ No newline at end of file