import { parseHTML } from 'linkedom' import { build } from 'esbuild' import { parse, compileScript } from '@vue/compiler-sfc' import { readFile, mkdir } from 'node:fs/promises' import { resolve, relative } from 'node:path' import { execFileSync } from 'node:child_process' import { pathToFileURL } from 'node:url' // A DOM-like test host, not a browser or a media decoder. Install it before importing Vue. const { window } = parseHTML('') for (const key of ['window', 'document', 'HTMLElement', 'Element', 'Node', 'SVGElement', 'Event', 'CustomEvent', 'MutationObserver']) { Object.defineProperty(globalThis, key, { configurable: true, value: key === 'window' ? window : window[key] }) } Object.defineProperty(globalThis, 'navigator', { configurable: true, value: { userAgent: 'offline-video-test' } }) globalThis.location = new URL('http://fixture.invalid/') window.location = globalThis.location window.requestAnimationFrame = globalThis.requestAnimationFrame = (callback) => setTimeout(callback, 0) window.cancelAnimationFrame = globalThis.cancelAnimationFrame = clearTimeout const videoPrototype = Object.getPrototypeOf(document.createElement('video')) videoPrototype.play = () => Promise.resolve() videoPrototype.pause = () => {} videoPrototype.load = () => {} const baselineOnly = process.argv.includes('--baseline-only') const base = 'f437979141947a68c67cbc6797ae31f7aae1188f' const ownSources = new Set([ 'src/modules/cockpit/components/VideoMonitorEntry.vue', 'src/modules/cockpit/components/VideoMonitorPanel.vue', 'src/modules/cockpit/components/VideoPlayer.vue', 'src/modules/cockpit/services/videoApi.js', 'src/modules/cockpit/services/videoLiveSession.js', 'src/modules/cockpit/services/videoPlayer.js', 'src/modules/cockpit/stores/videoStore.js', 'src/pages/cockpit/index.vue', ]) async function sourceAt(path) { const name = relative(process.cwd(), path).replaceAll('\\', '/') if (!baselineOnly || !name.startsWith('src/') || ownSources.has(name)) return readFile(path, 'utf8') let source = execFileSync('git', ['show', `${base}:${name}`], { encoding: 'utf8' }) if (name === 'src/modules/cockpit/components/EquipmentDetailSections.vue') { const anchor = '
' if (source.split(anchor).length !== 2) throw new Error('Video entry insertion anchor must be unique') source = source.replace(anchor, `${anchor}\n `) .replace("import SectionStateTag from './SectionStateTag.vue'", "import VideoMonitorEntry from './VideoMonitorEntry.vue'\nimport SectionStateTag from './SectionStateTag.vue'") } return source } const output = resolve(`.scratch/cockpit-video-monitoring/test-output/journey${baselineOnly ? '-baseline' : ''}.mjs`) await mkdir(resolve('.scratch/cockpit-video-monitoring/test-output'), { recursive: true }) await build({ entryPoints: [resolve('tests/cockpit-video/journey.mjs')], outfile: output, bundle: true, platform: 'node', format: 'esm', packages: 'external', define: { 'import.meta.env': '{}', '__VUE_OPTIONS_API__': 'true', '__VUE_PROD_DEVTOOLS__': 'false' }, plugins: [{ name: 'offline-vue-sfc', setup(builder) { builder.onLoad({ filter: /\.vue$/ }, async ({ path }) => { const source = await sourceAt(path) const { descriptor, errors } = parse(source, { filename: path }) if (errors.length) throw errors[0] const script = compileScript(descriptor, { id: path, inlineTemplate: true }) return { contents: script.content, loader: 'js', resolveDir: resolve(path, '..') } }) builder.onLoad({ filter: /\.js$/ }, async ({ path }) => { if (!baselineOnly || !relative(process.cwd(), path).replaceAll('\\', '/').startsWith('src/')) return null return { contents: await sourceAt(path), loader: 'js', resolveDir: resolve(path, '..') } }) } }], }) await import(pathToFileURL(output).href)