Setting the file. One moment.
Frame Contract Test · PR To Video · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page scripts/frame-contract.test.mjs
scripts/ frame-contract.test.mjs
JavaScript · 209 lines · 7 KB
import
{ validateFrameHtml }
from
"./lib/frame-contract.mjs"
;
9
10 const scriptDir = dirname ( new URL ( import . meta .url).pathname);
11 const buildFrameScript = join (scriptDir, "build-frame.mjs" );
12 const assembleScript = join (scriptDir, "assemble-index.mjs" );
13 const transitionsScript = join (scriptDir, "transitions.mjs" );
14
15 function write ( path , contents ) {
16 mkdirSync ( dirname (path), { recursive: true });
17 writeFileSync (path, contents);
18 }
19
20 function validFrame ( id = "01-valid" , duration = 3 ) {
21 return `<template id="${ id }-template">
22 <div data-composition-id="${ id }" data-width="1920" data-height="1080" data-duration="${ duration }">
23 <div class="clip" data-start="0" data-duration="${ duration }" data-track-index="0"></div>
24 <style>#root { color: black; }</style>
25 <script>window.__timelines = window.__timelines || {}; window.__timelines["${ id }"] = gsap.timeline({ paused: true });</script>
26 </div>
27 </template>` ;
28 }
29
30 test ( "valid bare template fragment passes the shared frame contract" , () => {
31 assert. doesNotThrow (() =>
32 validateFrameHtml ( validFrame (), { expectedId: "01-valid" , expectedDuration: 3 }),
33 );
34 });
35
36 test ( "HTML5 unquoted root attributes are accepted" , () => {
37 assert. deepEqual (
38 validateFrameHtml (
39 `<template><div data-composition-id=01-valid data-duration=3></div></template>` ,
40 { expectedId: "01-valid" , expectedDuration: 3 },
41 ),
42 { compositionId: "01-valid" , duration: 3 },
43 );
44 });
45
46 test ( "duration accepts decimal seconds but rejects alternate numeric syntaxes" , () => {
47 assert. doesNotThrow (() =>
48 validateFrameHtml (
49 `<template><div data-composition-id="01-valid" data-duration="3.25"></div></template>` ,
50 ),
51 );
52 for ( const duration of [ "0x10" , "3e2" , "Infinity" ]) {
53 assert. throws (
54 () =>
55 validateFrameHtml (
56 `<template><div data-composition-id="01-valid" data-duration="${ duration }"></div></template>` ,
57 ),
58 /positive . * duration/ i ,
59 );
60 }
61 });
62
63 test ( "full HTML is rejected even when it contains a valid inner template" , () => {
64 const html = `<!doctype html><html><head></head><body>${ validFrame () }</body></html>` ;
65 assert. throws (
66 () => validateFrameHtml (html, { expectedId: "01-valid" , expectedDuration: 3 }),
67 /bare <template> | full HTML document/ i ,
68 );
69 });
70
71 test ( "missing or mismatched composition id and duration fail explicitly" , () => {
72 assert. throws (
73 () =>
74 validateFrameHtml ( `<template><div data-duration="3"></div></template>` , {
75 expectedId: "01-valid" ,
76 expectedDuration: 3 ,
77 }),
78 /composition id/ i ,
79 );
80 assert. throws (
81 () =>
82 validateFrameHtml (
83 `<template><div data-composition-id="wrong" data-duration="3"></div></template>` ,
84 { expectedId: "01-valid" , expectedDuration: 3 },
85 ),
86 /expected . * 01-valid/ i ,
87 );
88 assert. throws (
89 () =>
90 validateFrameHtml (
91 `<template><div data-composition-id="01-valid" data-duration="0"></div></template>` ,
92 { expectedId: "01-valid" , expectedDuration: 3 },
93 ),
94 /positive . * duration/ i ,
95 );
96 });
97
98 test ( "assembler rejects malformed worker output before writing index.html" , () => {
99 const project = mkdtempSync ( join ( tmpdir (), "p2v-frame-contract-" ));
100 write (
101 join (project, "STORYBOARD.md" ),
102 `--- \n format: 1920x1080 \n --- \n\n ## Frame 1 — Broken \n\n - duration: 3s \n - status: animated \n - src: compositions/frames/01-broken.html \n ` ,
103 );
104 write (
105 join (project, "compositions" , "frames" , "01-broken.html" ),
106 `<!doctype html><html><body><div data-composition-id="01-broken" data-duration="3"></div></body></html>` ,
107 );
108
109 assert. throws (
110 () =>
111 execFileSync (
112 process.execPath,
113 [assembleScript, "--storyboard" , join (project, "STORYBOARD.md" ), "--hyperframes" , project],
114 { encoding: "utf8" , stdio: "pipe" },
115 ),
116 /bare <template> | full HTML document/ i ,
117 );
118 assert. equal ( existsSync ( join (project, "index.html" )), false );
119 });
120
121 test ( "validated bare frames survive assembly and transition injection" , () => {
122 const project = mkdtempSync ( join ( tmpdir (), "p2v-frame-transition-" ));
123 write (
124 join (project, "STORYBOARD.md" ),
125 `--- \n format: 1920x1080 \n --- \n\n ## Frame 1 — First \n\n - duration: 3s \n - transition_in: cut \n - status: animated \n - src: compositions/frames/01-first.html \n\n ## Frame 2 — Second \n\n - duration: 3s \n - transition_in: crossfade 0.4s \n - status: animated \n - src: compositions/frames/02-second.html \n ` ,
126 );
127 write ( join (project, "compositions" , "frames" , "01-first.html" ), validFrame ( "01-first" , 3 ));
128 write ( join (project, "compositions" , "frames" , "02-second.html" ), validFrame ( "02-second" , 3 ));
129
130 execFileSync (
131 process.execPath,
132 [assembleScript, "--storyboard" , join (project, "STORYBOARD.md" ), "--hyperframes" , project],
133 { encoding: "utf8" },
134 );
135 execFileSync (
136 process.execPath,
137 [
138 transitionsScript,
139 "inject" ,
140 "--storyboard" ,
141 join (project, "STORYBOARD.md" ),
142 "--hyperframes" ,
143 project,
144 ],
145 { encoding: "utf8" },
146 );
147 const verified = execFileSync (
148 process.execPath,
149 [
150 transitionsScript,
151 "verify" ,
152 "--storyboard" ,
153 join (project, "STORYBOARD.md" ),
154 "--index" ,
155 join (project, "index.html" ),
156 ],
157 { encoding: "utf8" },
158 );
159 assert. match (verified, /1 transition \( s \) verified/ );
160 assert. doesNotThrow (() =>
161 execFileSync (
162 process.execPath,
163 [assembleScript, "--storyboard" , join (project, "STORYBOARD.md" ), "--hyperframes" , project],
164 { encoding: "utf8" },
165 ),
166 );
167 });
168
169 test ( "Code editorial preset stages renderer-parity fonts for an empty PR token set" , () => {
170 const project = mkdtempSync ( join ( tmpdir (), "p2v-code-editorial-fonts-" ));
171 write ( join (project, "capture" , "extracted" , "tokens.json" ), '{"colors":[],"fonts":[]}' );
172
173 execFileSync (
174 process.execPath,
175 [buildFrameScript, "--preset" , "code-editorial" , "--hyperframes" , project],
176 { encoding: "utf8" },
177 );
178
179 const expected = [
180 "EBGaramond-400.woff2" ,
181 "EBGaramond-700.woff2" ,
182 "Inter-400.woff2" ,
183 "Inter-700.woff2" ,
184 "JetBrainsMono-400.woff2" ,
185 "JetBrainsMono-700.woff2" ,
186 ];
187 for ( const name of expected) {
188 const path = join (project, "assets" , "fonts" , name);
189 assert. equal ( existsSync (path), true , `${ name } should be staged` );
190 const magic = readFileSync (path). subarray ( 0 , 4 ). toString ( "ascii" );
191 assert. equal (magic, "wOF2" , `${ name } should be a WOFF2 file` );
192 }
193
194 const frameMd = readFileSync ( join (project, "frame.md" ), "utf8" );
195 assert. match (frameMd, /@font-face \{ font-family:"EB Garamond";font-weight:400/ );
196 assert. match (frameMd, /@font-face \{ font-family:"Inter";font-weight:700/ );
197 assert. match (frameMd, /@font-face \{ font-family:"JetBrains Mono";font-weight:400/ );
198 assert. doesNotMatch (frameMd, /fonts \. googleapis \. com/ );
199 });
200
201 test ( "bundled Code editorial font licenses are shipped beside the assets" , () => {
202 const fontDir = resolve (
203 scriptDir,
204 "../../hyperframes-creative/frame-presets/code-editorial/fonts" ,
205 );
206 for ( const family of [ "eb-garamond" , "inter" , "jetbrains-mono" ]) {
207 assert. equal ( existsSync ( join (fontDir, `OFL-${ family }.txt` )), true );
208 }
209 });