run.mjs 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. import { parseHTML } from 'linkedom'
  2. import { build } from 'esbuild'
  3. import { parse, compileScript } from '@vue/compiler-sfc'
  4. import { readFile, mkdir } from 'node:fs/promises'
  5. import { resolve, relative } from 'node:path'
  6. import { execFileSync } from 'node:child_process'
  7. import { pathToFileURL } from 'node:url'
  8. // A DOM-like test host, not a browser or a media decoder. Install it before importing Vue.
  9. const { window } = parseHTML('<html><body></body></html>')
  10. for (const key of ['window', 'document', 'HTMLElement', 'Element', 'Node', 'SVGElement', 'Event', 'CustomEvent', 'MutationObserver']) {
  11. Object.defineProperty(globalThis, key, { configurable: true, value: key === 'window' ? window : window[key] })
  12. }
  13. Object.defineProperty(globalThis, 'navigator', { configurable: true, value: { userAgent: 'offline-video-test' } })
  14. globalThis.location = new URL('http://fixture.invalid/')
  15. window.location = globalThis.location
  16. window.requestAnimationFrame = globalThis.requestAnimationFrame = (callback) => setTimeout(callback, 0)
  17. window.cancelAnimationFrame = globalThis.cancelAnimationFrame = clearTimeout
  18. const videoPrototype = Object.getPrototypeOf(document.createElement('video'))
  19. videoPrototype.play = () => Promise.resolve()
  20. videoPrototype.pause = () => {}
  21. videoPrototype.load = () => {}
  22. const baselineOnly = process.argv.includes('--baseline-only')
  23. const base = 'f437979141947a68c67cbc6797ae31f7aae1188f'
  24. const ownSources = new Set([
  25. 'src/modules/cockpit/components/VideoMonitorEntry.vue', 'src/modules/cockpit/components/VideoMonitorPanel.vue',
  26. 'src/modules/cockpit/components/VideoPlayer.vue', 'src/modules/cockpit/services/videoApi.js',
  27. 'src/modules/cockpit/services/videoLiveSession.js', 'src/modules/cockpit/services/videoPlayer.js',
  28. 'src/modules/cockpit/stores/videoStore.js', 'src/pages/cockpit/index.vue',
  29. ])
  30. async function sourceAt(path) {
  31. const name = relative(process.cwd(), path).replaceAll('\\', '/')
  32. if (!baselineOnly || !name.startsWith('src/') || ownSources.has(name)) return readFile(path, 'utf8')
  33. let source = execFileSync('git', ['show', `${base}:${name}`], { encoding: 'utf8' })
  34. if (name === 'src/modules/cockpit/components/EquipmentDetailSections.vue') {
  35. const anchor = ' <div class="equipment-tab__body" role="tabpanel" data-equipment-tab-body>'
  36. if (source.split(anchor).length !== 2) throw new Error('Video entry insertion anchor must be unique')
  37. source = source.replace(anchor, `${anchor}\n <VideoMonitorEntry v-if="activeTab === 'metrics'" :device-code="deviceCode" />`)
  38. .replace("import SectionStateTag from './SectionStateTag.vue'", "import VideoMonitorEntry from './VideoMonitorEntry.vue'\nimport SectionStateTag from './SectionStateTag.vue'")
  39. }
  40. return source
  41. }
  42. const output = resolve(`.scratch/cockpit-video-monitoring/test-output/journey${baselineOnly ? '-baseline' : ''}.mjs`)
  43. await mkdir(resolve('.scratch/cockpit-video-monitoring/test-output'), { recursive: true })
  44. await build({
  45. entryPoints: [resolve('tests/cockpit-video/journey.mjs')], outfile: output,
  46. bundle: true, platform: 'node', format: 'esm', packages: 'external',
  47. define: { 'import.meta.env': '{}', '__VUE_OPTIONS_API__': 'true', '__VUE_PROD_DEVTOOLS__': 'false' },
  48. plugins: [{ name: 'offline-vue-sfc', setup(builder) {
  49. builder.onLoad({ filter: /\.vue$/ }, async ({ path }) => {
  50. const source = await sourceAt(path)
  51. const { descriptor, errors } = parse(source, { filename: path })
  52. if (errors.length) throw errors[0]
  53. const script = compileScript(descriptor, { id: path, inlineTemplate: true })
  54. return { contents: script.content, loader: 'js', resolveDir: resolve(path, '..') }
  55. })
  56. builder.onLoad({ filter: /\.js$/ }, async ({ path }) => {
  57. if (!baselineOnly || !relative(process.cwd(), path).replaceAll('\\', '/').startsWith('src/')) return null
  58. return { contents: await sourceAt(path), loader: 'js', resolveDir: resolve(path, '..') }
  59. })
  60. } }],
  61. })
  62. await import(pathToFileURL(output).href)