test: ajout 22 scripts TC (TC-08 a TC-31) + tc-header.sh partage
This commit is contained in:
Executable
+240
@@ -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
|
||||
Executable
+90
@@ -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
|
||||
Executable
+95
@@ -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
|
||||
Executable
+67
@@ -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
|
||||
Executable
+116
@@ -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
|
||||
Executable
+77
@@ -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
|
||||
Executable
+55
@@ -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
|
||||
Executable
+132
@@ -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
|
||||
Executable
+101
@@ -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
|
||||
Executable
+38
@@ -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
|
||||
Executable
+80
@@ -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: '<p>Test</p>',
|
||||
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: '<p>Test</p>',
|
||||
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
|
||||
Executable
+44
@@ -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
|
||||
Executable
+38
@@ -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
|
||||
Executable
+113
@@ -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
|
||||
Executable
+59
@@ -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
|
||||
Executable
+35
@@ -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
|
||||
Executable
+37
@@ -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
|
||||
Executable
+119
@@ -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
|
||||
Executable
+42
@@ -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: '<p>Test</p>'}, {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
|
||||
Executable
+35
@@ -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
|
||||
Executable
+38
@@ -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
|
||||
Executable
+55
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user