test(ambilight): [PKG-AMBILIGHT-05] checkpoint installed canvas combinations

This commit is contained in:
Harvey Zhao committed 2026-09-14 17:50:38 +08:00
1 parent e7f31b6fc8
commit 765ddb816f
19 files changed
+1032 -85

No files matched your search

+21 -3
View File
@@ -11,8 +11,20 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
const scenario = core === 'published-5.1.7' ? 'historical core has no proxy option and retains native Ambilight playback' : 'Ambilight samples all nine canvas proxy regions after output resizing'
test(`${core}: ${scenario}`, async ({ page }, testInfo) => {
await page.goto(`/test/player.html?core=${core}`)
for (const [name, source] of [['ambilightFactory', implementation.source], ['canvasProxy', proxy]])
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports; ${source}; window.${name} = module.exports.default || module.exports; })();` })
for (const [name, source] of [['ambilightFactory', implementation.source], ['canvasProxy', proxy]]) {
await page.addScriptTag({ content: `(() => {
const pending = new Set();
const requestAnimationFrame = callback => {
const id = window.requestAnimationFrame(time => { pending.delete(id); callback(time); });
pending.add(id); return id;
};
const cancelAnimationFrame = id => { pending.delete(id); window.cancelAnimationFrame(id); };
const module = { exports: {} }; const exports = module.exports;
${source};
window.${name} = module.exports.default || module.exports;
(window.combinationFrames ||= {})['${name}'] = pending;
})();` })
}
await page.evaluate(() => {
window.palette = ['rgb(255, 0, 0)', 'rgb(0, 255, 0)', 'rgb(0, 0, 255)', 'rgb(255, 255, 0)', 'rgb(0, 255, 255)', 'rgb(255, 0, 255)', 'rgb(120, 0, 0)', 'rgb(0, 120, 0)', 'rgb(0, 0, 120)']
window.drawCount = 0
@@ -61,8 +73,14 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
expect(dimensions.canvas).not.toEqual(dimensions.video)
await testInfo.attach('ambilight-proxy-inputs', { contentType: 'application/json', body: JSON.stringify({ core, dimensions, plugin: hash(implementation.source), proxy: hash(proxySource), media: '/test/pattern.mp4', scope: 'real video decode and proxy post-processing callback paints a nine-cell palette using native Canvas' }) })
await expect.poll(() => page.evaluate(() => [...document.querySelectorAll('.artplayer-plugin-ambilight > div')].map(node => node.style.backgroundColor))).toEqual(await page.evaluate(() => window.palette))
await page.evaluate(() => window.art.destroy())
const drawsAtDestroy = await page.evaluate(async () => {
window.art.destroy()
const draws = window.drawCount
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))
return draws
})
expect(await page.locator('.artplayer-plugin-ambilight').count()).toBe(0)
expect(await page.evaluate(() => ({ draws: window.drawCount, pluginFrames: window.combinationFrames.ambilightFactory.size, proxyFrames: window.combinationFrames.canvasProxy.size }))).toEqual({ draws: drawsAtDestroy, pluginFrames: 0, proxyFrames: 0 })
})
}
+9 -1
View File
@@ -1,4 +1,5 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
@@ -6,12 +7,19 @@ import vm from 'node:vm'
import { build, transform } from 'esbuild'
import { verifyAmbilightContract } from '../../refactor/scripts/ambilight-contract.mjs'
import { readMember } from '../../refactor/scripts/releases.mjs'
import { verifyInstalledArtifacts } from '../../scripts/installed-artifacts.mjs'
import { getEntryFile } from '../../scripts/projects.js'
export async function ambilightCandidate() {
const root = fileURLToPath(new URL('../../', import.meta.url))
if (process.env.ARTPLAYER_BROWSER_ARTIFACTS) {
assert.notEqual(process.env.ARTPLAYER_AMBILIGHT_BASELINE, '1', 'Installed candidate cannot use the frozen workspace')
const { inputs } = verifyInstalledArtifacts(root, process.env.ARTPLAYER_BROWSER_ARTIFACTS, ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight'])
const input = inputs.find(item => item.name === 'artplayer-plugin-ambilight')
return { name: 'candidate-installed', source: fs.readFileSync(input.file, 'utf8'), format: 'artifact', provenance: input }
}
if (process.env.ARTPLAYER_AMBILIGHT_BASELINE === '1')
return (await ambilightHistorical()).find(item => item.name === 'frozen-workspace')
const root = fileURLToPath(new URL('../../', import.meta.url))
const result = await build({ entryPoints: [getEntryFile(path.join(root, 'packages/artplayer-plugin-ambilight'))], bundle: true, write: false, platform: 'browser', format: 'cjs', target: 'es2020' })
return { name: 'candidate-source', source: result.outputFiles[0].text, format: 'artifact' }
}
+10 -1
View File
@@ -1,3 +1,5 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
@@ -5,12 +7,19 @@ import vm from 'node:vm'
import { build, transform } from 'esbuild'
import { verifyCanvasContract } from '../../refactor/scripts/canvas-contract.mjs'
import { readMember } from '../../refactor/scripts/releases.mjs'
import { verifyInstalledArtifacts } from '../../scripts/installed-artifacts.mjs'
import { getEntryFile } from '../../scripts/projects.js'
export async function canvasCandidate() {
const root = fileURLToPath(new URL('../../', import.meta.url))
if (process.env.ARTPLAYER_BROWSER_ARTIFACTS) {
assert.notEqual(process.env.ARTPLAYER_CANVAS_BASELINE, '1', 'Installed candidate cannot use the frozen workspace')
const { inputs } = verifyInstalledArtifacts(root, process.env.ARTPLAYER_BROWSER_ARTIFACTS, ['artplayer', 'artplayer-plugin-chapter', 'artplayer-proxy-canvas'])
const input = inputs.find(item => item.name === 'artplayer-proxy-canvas')
return { name: 'candidate-installed', source: fs.readFileSync(input.file, 'utf8'), format: 'artifact', provenance: input }
}
if (process.env.ARTPLAYER_CANVAS_BASELINE === '1')
return (await canvasHistorical()).find(item => item.name === 'frozen-workspace')
const root = fileURLToPath(new URL('../../', import.meta.url))
const result = await build({ entryPoints: [getEntryFile(path.join(root, 'packages/artplayer-proxy-canvas'))], bundle: true, write: false, platform: 'browser', format: 'cjs', target: 'es2020' })
return { name: 'candidate-source', source: result.outputFiles[0].text, format: 'artifact' }
}
+32 -1
View File
@@ -1,11 +1,42 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
// eslint-disable-next-line test/no-import-node-test -- This fixture exercises the repository's Node test runner.
import { test } from 'node:test'
import { checkFiles, publishedConsumer } from '../scripts/package-check.mjs'
import { checkFiles, checkPackages, publishedConsumer } from '../scripts/package-check.mjs'
import { removeConsumer, runtimeConsumer } from '../scripts/package-consumer.mjs'
import { emitterContracts } from './contracts/emitter.js'
import { ambilightCandidate } from './helpers/ambilight.js'
import { canvasCandidate } from './helpers/canvas.js'
test('Additional browser packages cannot be mistaken for full release consumer acceptance', async () => {
await assert.rejects(checkPackages({ release: true, include: ['artplayer-plugin-ambilight'] }), /not full release acceptance/)
})
test('Explicit installed plugin maps never fall back to source or frozen workspace', async () => {
const keys = ['ARTPLAYER_BROWSER_ARTIFACTS', 'ARTPLAYER_AMBILIGHT_BASELINE', 'ARTPLAYER_CANVAS_BASELINE']
const previous = Object.fromEntries(keys.map(key => [key, process.env[key]]))
try {
process.env.ARTPLAYER_BROWSER_ARTIFACTS = path.resolve('refactor/.cache/absent-installed-plugin-map.json')
assert(!fs.existsSync(process.env.ARTPLAYER_BROWSER_ARTIFACTS))
delete process.env.ARTPLAYER_AMBILIGHT_BASELINE
delete process.env.ARTPLAYER_CANVAS_BASELINE
await assert.rejects(ambilightCandidate(), { code: 'ENOENT' })
await assert.rejects(canvasCandidate(), { code: 'ENOENT' })
process.env.ARTPLAYER_AMBILIGHT_BASELINE = '1'
process.env.ARTPLAYER_CANVAS_BASELINE = '1'
await assert.rejects(ambilightCandidate(), /cannot use the frozen workspace/)
await assert.rejects(canvasCandidate(), /cannot use the frozen workspace/)
}
finally {
for (const key of keys) {
if (previous[key] === undefined)
delete process.env[key]
else process.env[key] = previous[key]
}
}
})
test('Package checks reject missing files, missing wildcard exports and internal configuration', () => {
const manifest = { name: 'fixture', main: './dist/main.js', module: './dist/main.mjs', types: './types/main.d.ts', legacy: './dist/legacy.js', exports: { '.': './dist/main.js', './lang/*': './dist/lang/*.js' } }
+15 -2
View File
@@ -1,8 +1,14 @@
# Installed package checks
Run `yarn test:package` with the pinned Node and Yarn after a frozen install. The
initial scope is core and chapter; extend `names` in `scripts/package-consumer.mjs`
and add package-specific consumers when migrating another package.
default consumer scope is core and chapter. For Ambilight/Canvas browser work,
run `yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas`.
These two additional packages have reviewed published contracts and use the same
source snapshot/build/pack/offline-install/frozen-reinstall pipeline. Installed
files are copied only after archive hash checks. The report explicitly separates
the core/chapter runtime/type scope from the additional browser package scope;
`--release` rejects additional packages until complete consumer coverage exists.
Other packages need a reviewed contract and their own consumers before inclusion.
`scripts/package-check.mjs` copies source into an ignored build snapshot, rebuilds
all three formats and core languages using repository scripts, and runs Yarn pack.
@@ -72,6 +78,13 @@ and run `yarn test:browser`. The service then uses installed package bytes witho
falling back to source. CI performs this sequence and uploads only reports and
artifacts, excluding the build snapshot's node_modules link.
`scripts/installed-artifacts.mjs` verifies selected packages against the installed
report and the current source/build inputs; performance retains its existing
two-package wrapper. Ambilight and Canvas browser helpers use this verifier when
an artifact map is supplied, including optional legacy maps pointing to checked
legacy members in the same installation. Missing packages, stale source, modified
bundles or contradictory frozen-workspace flags fail instead of rebuilding source.
`test/package-check.test.js` verifies missing targets, leaked configuration and
removed historical files, then deliberately removes an actual published default
export and required entry in an isolated consumer to prove runtime failures are
+10 -2
View File
@@ -6,6 +6,7 @@ import process from 'node:process'
import { test } from 'node:test'
import { fileURLToPath } from 'node:url'
import { hash } from '../refactor/scripts/releases.mjs'
import { verifyInstalledArtifacts } from '../scripts/installed-artifacts.mjs'
import { verifyPerformanceArtifacts } from '../scripts/performance-artifacts.mjs'
import { performanceHtml, performanceScript, waitForObservation } from '../scripts/performance-fixture.mjs'
import { validatePairedPerformance } from '../scripts/performance-report.mjs'
@@ -128,7 +129,7 @@ test('performance refuses stale sources, build tools and altered installed bundl
write(path.join(root, file), content)
write(path.join(snapshot, file), content)
}
const names = ['artplayer', 'artplayer-plugin-chapter']
const names = ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight']
const packages = names.map((name) => {
const entry = `packages/${name}/src/index.ts`
write(path.join(root, entry), 'export default 1\n')
@@ -144,6 +145,13 @@ test('performance refuses stale sources, build tools and altered installed bundl
write(map, JSON.stringify(Object.fromEntries(names.map(name => [name, `artifacts/${name}/dist/${name}.js`]))))
write(path.join(output, 'report.json'), JSON.stringify({ task: 'ENG-07', knownTypeBlockers: 0, node: process.versions.node, packages }))
assert.equal(verifyPerformanceArtifacts(root, map).inputs.length, 2)
assert.equal(verifyPerformanceArtifacts(root, map).packages.length, 2)
assert.equal(verifyInstalledArtifacts(root, map, names).inputs.length, 3)
assert.throws(() => verifyInstalledArtifacts(root, map, [...names, 'artplayer-proxy-canvas']), /Missing installed package/)
const pluginSource = path.join(root, 'packages/artplayer-plugin-ambilight/src/index.ts')
write(pluginSource, 'export default 2\n')
assert.throws(() => verifyInstalledArtifacts(root, map, names), /Stale package source/)
write(pluginSource, 'export default 1\n')
const source = path.join(root, 'packages/artplayer/src/index.ts')
write(source, 'export default 2\n')
assert.throws(() => verifyPerformanceArtifacts(root, map), /Stale package source/)
@@ -158,5 +166,5 @@ test('performance refuses stale sources, build tools and altered installed bundl
assert.throws(() => verifyPerformanceArtifacts(root, map), /Stale library build inputs/)
fs.unlinkSync(path.join(snapshot, 'scripts/library/removed.ts'))
write(path.join(output, 'artifacts/artplayer/dist/artplayer.js'), 'changed bundle\n')
assert.throws(() => verifyPerformanceArtifacts(root, map), /Installed performance artifact changed/)
assert.throws(() => verifyPerformanceArtifacts(root, map), /Installed browser artifact changed/)
})