mirror of
https://github.com/Narcooo/inkos.git
synced 2026-08-30 17:22:02 +08:00
fix(core,studio): player Number() parity with evaluator + log skipped export images
This commit is contained in:
@@ -44,6 +44,24 @@ describe("buildPlayableHtml", () => {
|
||||
expect(html).toContain('<script>BAD</script>');
|
||||
});
|
||||
|
||||
// Determinism parity: relational ops must Number()-coerce both operands (mirrors evaluator.ts)
|
||||
it("player evalCond uses Number() coercion for relational ops (parity with evaluator)", () => {
|
||||
const html = buildPlayableHtml(graph);
|
||||
// All four relational ops must use Number(v) and Number(c.value)
|
||||
expect(html).toMatch(/Number\(v\)\s*>=\s*Number\(c\.value\)/);
|
||||
expect(html).toMatch(/Number\(v\)\s*<=\s*Number\(c\.value\)/);
|
||||
expect(html).toMatch(/Number\(v\)\s*>\s*Number\(c\.value\)/);
|
||||
expect(html).toMatch(/Number\(v\)\s*<\s*Number\(c\.value\)/);
|
||||
// Raw lexical comparison must not appear for relational ops
|
||||
expect(html).not.toMatch(/return v>=c\.value/);
|
||||
expect(html).not.toMatch(/return v<=c\.value/);
|
||||
expect(html).not.toMatch(/return v>c\.value/);
|
||||
expect(html).not.toMatch(/return v<c\.value/);
|
||||
// add/sub effects must Number()-coerce
|
||||
expect(html).toMatch(/Number\(vars\[e\.var\]\|\|0\)\+Number\(e\.value\)/);
|
||||
expect(html).toMatch(/Number\(vars\[e\.var\]\|\|0\)-Number\(e\.value\)/);
|
||||
});
|
||||
|
||||
// XSS: Fix 2 — node content embedded in JSON is escaped by esc() so the raw onerror attribute never appears
|
||||
it("does not embed raw onerror attribute from node content (Fix 2)", () => {
|
||||
const xssGraph = StoryGraphSchema.parse({
|
||||
|
||||
@@ -8,9 +8,9 @@ const PLAYER_JS = String.raw`
|
||||
var nodeById = {}; (GRAPH.nodes||[]).forEach(function(n){ nodeById[n.id] = n; });
|
||||
var endingByNode = {}; (GRAPH.endings||[]).forEach(function(e){ endingByNode[e.nodeId] = e; });
|
||||
function evalCond(c){ if(!c) return true; var v = vars[c.var];
|
||||
switch(c.op){ case ">=": return v>=c.value; case "<=": return v<=c.value; case ">": return v>c.value; case "<": return v<c.value; case "==": return v===c.value; case "!=": return v!==c.value; } return true; }
|
||||
switch(c.op){ case ">=": return Number(v)>=Number(c.value); case "<=": return Number(v)<=Number(c.value); case ">": return Number(v)>Number(c.value); case "<": return Number(v)<Number(c.value); case "==": return v===c.value; case "!=": return v!==c.value; } return true; }
|
||||
function applyEffects(effects){ (effects||[]).forEach(function(e){
|
||||
if(e.op==="add") vars[e.var]=(vars[e.var]||0)+e.value; else if(e.op==="sub") vars[e.var]=(vars[e.var]||0)-e.value; else vars[e.var]=e.value; }); }
|
||||
if(e.op==="add") vars[e.var]=Number(vars[e.var]||0)+Number(e.value); else if(e.op==="sub") vars[e.var]=Number(vars[e.var]||0)-Number(e.value); else vars[e.var]=e.value; }); }
|
||||
function visible(node){ return (node.choices||[]).filter(function(c){ return evalCond(c.condition); }); }
|
||||
var root = document.getElementById("if-player");
|
||||
function hud(){ var s = Object.keys(vars).map(function(k){ return h(k)+": "+h(String(vars[k])); }).join(" · "); return s ? '<div class="hud">'+s+'</div>' : ''; }
|
||||
|
||||
@@ -5270,8 +5270,8 @@ export function createStudioServer(initialConfig: ProjectConfig, root: string, o
|
||||
const file = resolveProjectImageFile(root, ref);
|
||||
const buf = await readFile(file.resolved);
|
||||
assetDataUris[ref] = `data:${file.contentType};base64,${buf.toString("base64")}`;
|
||||
} catch {
|
||||
/* missing or unreadable image — skip this assetRef, bundle is still generated */
|
||||
} catch (err) {
|
||||
console.warn(`[studio] export/html: skipping assetRef "${ref}" —`, err);
|
||||
}
|
||||
}
|
||||
return new Response(buildPlayableHtml(graph, { assetDataUris }), {
|
||||
|
||||
Reference in New Issue
Block a user