| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- 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.
- // Every exercised network seam must be explicitly injected; never fall through to a live endpoint.
- globalThis.fetch = async () => { throw new Error('Uninjected network request in offline video test') }
- const { window } = parseHTML('<html><body></body></html>')
- 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
- document.location = globalThis.location
- window.requestAnimationFrame = globalThis.requestAnimationFrame = (callback) => setTimeout(callback, 0)
- window.cancelAnimationFrame = globalThis.cancelAnimationFrame = clearTimeout
- // Native DOM nodes are not plain objects; preserve that contract for Vue refs in LinkeDOM.
- Object.defineProperty(window.HTMLElement.prototype, Symbol.toStringTag, { configurable: true, value: 'HTMLElement' })
- globalThis.getComputedStyle = window.getComputedStyle = (element) => ({ getPropertyValue: (name) => element.style?.getPropertyValue(name) || '' })
- if (process.argv.includes('--hosts')) {
- window.HTMLElement.prototype.getBoundingClientRect = function () {
- const detail = this.classList?.contains('cockpit-shell__detail')
- const width = detail ? 400 : 1600, height = detail ? 420 : 900
- return { x: 0, y: 0, left: 0, top: 0, width, height, right: width, bottom: height }
- }
- }
- // LinkeDOM exposes a read-only select.value; Vue expects the browser's writable contract.
- const selectPrototype = Object.getPrototypeOf(document.createElement('select'))
- const selectValue = Object.getOwnPropertyDescriptor(selectPrototype, 'value')
- Object.defineProperty(selectPrototype, 'value', { configurable: true, get: selectValue.get,
- set(value) { for (const option of this.options) option.selected = option.value === String(value) } })
- const videoPrototype = Object.getPrototypeOf(document.createElement('video'))
- videoPrototype.play = () => Promise.resolve()
- videoPrototype.pause = () => {}
- videoPrototype.load = () => {}
- const baselineOnly = process.argv.includes('--baseline-only')
- const suite = process.argv.includes('--hosts') ? 'hosts' : 'journey'
- const base = '668bdfc0e6313b9e75ae67524dc8ac7771326774'
- const sourceRef = process.argv.find((value) => value.startsWith('--source-ref='))?.slice('--source-ref='.length)
- || (baselineOnly ? base : '')
- if (sourceRef && !/^[a-f0-9]{7,40}$/.test(sourceRef)) throw new Error('Expected a fixed Git source revision')
- // Read a fixed baseline plus explicit owned files; never write baseline bytes over WIP.
- const workingSources = new Set(process.argv.filter((value) => value.startsWith('--working-source='))
- .map((value) => value.slice('--working-source='.length)))
- if (workingSources.size && !sourceRef) throw new Error('--working-source requires --source-ref')
- for (const name of workingSources) {
- if (!/^src\/[\w/.-]+$/.test(name) || name.includes('..')) throw new Error('Expected a repository source path')
- }
- if (sourceRef) console.log('Source snapshot:', sourceRef, 'working sources:', [...workingSources])
- async function sourceAt(path) {
- const name = relative(process.cwd(), path).replaceAll('\\', '/')
- if (!sourceRef || !name.startsWith('src/') || workingSources.has(name)) return readFile(path, 'utf8')
- return execFileSync('git', ['show', `${sourceRef}:${name}`], { encoding: 'utf8' })
- }
- const output = resolve(`.scratch/cockpit-video-monitoring/test-output/${suite}${baselineOnly ? '-baseline' : ''}.mjs`)
- await mkdir(resolve('.scratch/cockpit-video-monitoring/test-output'), { recursive: true })
- await build({
- entryPoints: [resolve(`tests/cockpit-video/${suite}.mjs`)], outfile: output,
- alias: { '@': resolve('src') },
- 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.onResolve({ filter: /\.css$/ }, ({ path }) => ({ path, namespace: 'offline-style' }))
- builder.onLoad({ filter: /.*/, namespace: 'offline-style' }, () => ({ contents: '', loader: 'js' }))
- if (suite === 'hosts') {
- // Only physical rendering adapters vary. mapService/Shell/selection stay production code.
- const engineMethods = ['createViewer', 'destroyViewer', 'resizeViewer', 'applyOverlays', 'sampleTerrainHeight', 'sampleTerrainHeights',
- 'createCameraEngine', 'groundHeightAt', 'markerGroundHeight', 'applyViewModeLayers', 'createOverlayReadiness',
- 'applyMotionResolution', 'hasFogStage', 'syncPointMarkers', 'syncAssociatedDevices', 'clearAssociatedDevices',
- 'clearPointMarkers', 'hasPointMarker', 'hasDeviceMarker', 'projectPointMarker', 'projectDeviceMarker',
- 'projectDeviceMarkers', 'projectOverlapSymbols', 'readPointMarkers', 'readAssociatedDevices', 'readDeviceEffects',
- 'applyDeviceEffects', 'applyDeviceReactions', 'clearDeviceLead', 'syncDeviceLead', 'setOverlapPresentation',
- 'readOverlapTargets', 'clearOverlapNudge', 'highlightEntities', 'syncPipeLayer', 'pickPipeObjectAt', 'noteFrameInterval', 'tickAdaptiveResolution']
- builder.onResolve({ filter: /(?:^|\/)cesiumEngine\.js$/ }, ({ path }) => ({ path, namespace: 'offline-gpu' }))
- builder.onLoad({ filter: /.*/, namespace: 'offline-gpu' }, () => ({ loader: 'js',
- contents: engineMethods.map((name) => `export const ${name} = (...args) => globalThis.__VIDEO_GPU__.${name}(...args);`).join('\n') }))
- builder.onResolve({ filter: /(?:^|\/)threePipeEnhancement\.js$/ }, ({ path, resolveDir, namespace }) => namespace === 'offline-three-real'
- ? null : ({ path: resolve(resolveDir, path), namespace: 'offline-three-wrapper' }))
- builder.onLoad({ filter: /.*/, namespace: 'offline-three-wrapper' }, ({ path }) => ({ loader: 'js', resolveDir: resolve(path, '..'),
- contents: `import { mountPipeEnhancement as mount } from 'offline-three-real'; export function mountPipeEnhancement(host, plan, options) { return mount(host, plan, { ...globalThis.__VIDEO_THREE_OPTIONS__, ...options }); }` }))
- builder.onResolve({ filter: /^offline-three-real$/ }, () => ({ path: resolve('src/modules/cockpit/services/threePipeEnhancement.js'), namespace: 'offline-three-real' }))
- builder.onLoad({ filter: /.*/, namespace: 'offline-three-real' }, async ({ path }) => ({ contents: await sourceAt(path), loader: 'js', resolveDir: resolve(path, '..') }))
- }
- 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 (!sourceRef || !relative(process.cwd(), path).replaceAll('\\', '/').startsWith('src/')) return null
- return { contents: await sourceAt(path), loader: 'js', resolveDir: resolve(path, '..') }
- })
- } }],
- })
- await import(pathToFileURL(output).href)
|