File size: 4,250 Bytes
bc50ea0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
import {
    parsePlanJson, planToJson, normalizeChapterResolution, orderedChapters, safeShotId,
} from "../web/h3_chain_plan_core.mjs";

const input = {
    shots:[{id:"one", prompt:"First"}, {id:"two", prompt:"Next"}],
    chapters:[{id:"opening", title:"Opening", start_scene_id:"one", text:"Notes"},
        {id:"sequel", title:"Sequel", start_scene_id:"two", text:"", resolution:{width:128, height:96}}],
};
const plan = parsePlanJson(JSON.stringify(input));
assert.equal(plan.chapters[0].resolution, undefined, "old chapter markers inherit without new serialized defaults");
assert.deepEqual(parsePlanJson(planToJson(plan)).chapters[1].resolution, {width:128, height:96});
for (const value of ["128x96", {}, {width:65, height:64}, {width:64}, {width:32, height:0},
    {width:true, height:64}, {width:64, height:64, extra:true}, {width:16416, height:64}]) {
    assert.throws(() => normalizeChapterResolution(value));
    assert.throws(() => parsePlanJson(JSON.stringify({...input, chapters:[{...input.chapters[0], resolution:value}]})));
}
assert.equal(normalizeChapterResolution(null), null);

// Exercise the actual chapter-menu change handlers, not just schema helpers.
const source = fs.readFileSync(new URL("../web/h3_chain_plan_studio.js", import.meta.url), "utf8");
const render = source.match(/^    function renderChapterPanel\([^]*?^    }$/m)?.[0];
assert.ok(render);
function element(tag, className="", textContent="") {
    return {tag, className, textContent, children:[], listeners:{}, value:"", disabled:false,
        append(...children) { this.children.push(...children); },
        addEventListener(name, handler) { this.listeners[name] = handler; },
    };
}
const fields = {};
let writes = 0;
const state = {plan, activeChapterId:"sequel"};
const context = vm.createContext({
    state, node:{}, orderedChapters, safeShotId, normalizeChapterResolution, element,
    widget:(_node, key) => ({value:key === "width" ? 64 : 96}),
    field:(label, control) => { fields[label] = control; return control; },
    button:() => element("button"), writePlan:() => { writes++; },
    renderTimeline(){}, renderShell(){}, renderScenePanel(){}, confirm:() => false,
});
vm.runInContext(render, context);
const panel = context.renderChapterPanel();
const form = panel.children.find(child => child.className === "h3studio-chapter-settings");
assert.ok(form, "chapter settings use the available panel width, not the two-column defaults grid");
const resolutionRow = form.children.find(child => child.className === "h3studio-chapter-resolution");
assert.deepEqual([...resolutionRow.children], [fields.Resolution, fields.Width, fields.Height],
    "resolution mode and dimensions stay together in a compact row");
assert.match(source, /\.h3studio-chapter-settings \{ display:flex; flex-wrap:wrap;/);
assert.match(source, /\.h3studio-chapter-resolution \{ display:flex; flex:2 1 344px; flex-wrap:wrap;/);
assert.equal(fields.Resolution.value, "custom");
assert.equal(fields.Width.value, "128");
assert.equal(fields.Height.value, "96");
fields.Width.value = "192";
fields.Width.listeners.change();
assert.equal(state.plan.chapters[1].resolution.width, 192);
assert.equal(writes, 1);
fields.Height.value = "99";
fields.Height.listeners.change();
assert.equal(state.plan.chapters[1].resolution.height, 96, "invalid edits do not corrupt the Plan");
assert.equal(writes, 1);
assert.ok(panel.children.some(child => child.textContent.includes("32")));
fields.Resolution.value = "inherit";
fields.Resolution.listeners.change();
assert.equal(state.plan.chapters[1].resolution, undefined);
assert.equal(fields.Width.disabled, true);
assert.equal(fields.Height.disabled, true);
fields.Height.value = "96";
fields.Resolution.value = "custom";
fields.Resolution.listeners.change();
assert.equal(fields.Width.disabled, false);
assert.deepEqual(JSON.parse(JSON.stringify(state.plan.chapters[1].resolution)), {width:192, height:96});
assert.equal(writes, 3);
assert.equal(state.plan.chapters[0].resolution, undefined, "changing Chapter 2 leaves Chapter 1 alone");
console.log("Chapter resolution: schema round-trip, legacy inheritance and real menu handlers pass");