import test from 'node:test' import assert from 'node:assert/strict' import path from 'node:path' import { fileURLToPath } from 'node:url' import { validateAssetBuildManifest } from '../scripts/asset-build-manifest.mjs' const testDirectory = path.dirname(fileURLToPath(import.meta.url)) const workspace = path.resolve(testDirectory, '..', '..') const baseAsset = () => ({ id: 'test-asset', source: 'docs/design/assets/source.png', output: 'static/assets/output.png', sourcePixels: { width: 1536, height: 3840 }, outputPixels: { width: 1440, height: 3600 }, quality: { maxBytes: 7000000, colorSpace: 'sRGB' }, }) // schema v3 以 processing.mode 为严格判别字段。每个分支只允许自身真正消费的 // 参数,避免给不透明背景伪造透明边、色键或切片字段来“凑齐”旧结构。 const chromaAsset = () => ({ ...baseAsset(), alpha: { required: true, transparentOuterPadding: 6, cornerMaxAlpha: 0 }, edge: { forbidChromaResidue: true, forbidLightFringe: true, premultipliedAlphaCheck: true }, processing: { mode: 'chroma-stretch', capWidth: 380, keyColor: '#00FF00', keyTolerance: 96, }, }) const opaqueResizeAsset = () => ({ ...baseAsset(), processing: { mode: 'opaque-resize', resample: 'lanczos', outputMode: 'RGB' }, }) const opaqueCoverAsset = () => ({ ...baseAsset(), processing: { mode: 'opaque-cover-crop', resample: 'lanczos', anchor: 'center', outputMode: 'RGB', }, }) const warmFrameAsset = () => ({ ...baseAsset(), alpha: { required: true, transparentOuterPadding: 0, cornerMaxAlpha: 0 }, edge: { forbidChromaResidue: false, forbidLightFringe: false, premultipliedAlphaCheck: true }, processing: { mode: 'warm-gold-frame-extract', borderBand: 110, redGreenMin: 15, greenBlueMin: 12, redBlueMin: 35, redMaxExclusive: 245, blueMaxExclusive: 180, alphaOffset: 25, alphaScale: 6, outputMode: 'RGBA', }, }) const manifestWith = (asset) => ({ schemaVersion: 3, kind: 'asset-build-manifest', family: 'test-family-v3', assets: [asset], }) test('接受四种职责严格分离的 schema v3 处理模式', () => { for (const factory of [chromaAsset, opaqueResizeAsset, opaqueCoverAsset, warmFrameAsset]) { const manifest = manifestWith(factory()) assert.equal(validateAssetBuildManifest(manifest, workspace), manifest) } }) test('拒绝没有 processing.mode 的旧 schema v3 形状', () => { const asset = chromaAsset() delete asset.processing.mode assert.throws(() => validateAssetBuildManifest(manifestWith(asset), workspace), /processing\.mode/i) }) test('不透明分支拒绝透明度、边缘和其他模式的参数', () => { const withAlpha = opaqueResizeAsset() withAlpha.alpha = chromaAsset().alpha assert.throws(() => validateAssetBuildManifest(manifestWith(withAlpha), workspace), /unknown field/i) const withAnchor = opaqueResizeAsset() withAnchor.processing.anchor = 'center' assert.throws(() => validateAssetBuildManifest(manifestWith(withAnchor), workspace), /unknown field/i) const withColorKey = opaqueCoverAsset() withColorKey.processing.keyColor = '#00FF00' assert.throws(() => validateAssetBuildManifest(manifestWith(withColorKey), workspace), /unknown field/i) }) test('透明分支拒绝缺失 alpha/edge 及不属于自身的处理字段', () => { const missingAlpha = chromaAsset() delete missingAlpha.alpha assert.throws(() => validateAssetBuildManifest(manifestWith(missingAlpha), workspace), /alpha/i) const missingEdge = warmFrameAsset() delete missingEdge.edge assert.throws(() => validateAssetBuildManifest(manifestWith(missingEdge), workspace), /edge/i) const wrongProcessing = warmFrameAsset() wrongProcessing.processing.capWidth = 380 assert.throws(() => validateAssetBuildManifest(manifestWith(wrongProcessing), workspace), /unknown field/i) }) test('拒绝重复输出、越界路径、未知字段和未知模式', () => { const duplicate = manifestWith(chromaAsset()) duplicate.assets.push({ ...chromaAsset(), id: 'duplicate-output' }) assert.throws(() => validateAssetBuildManifest(duplicate, workspace), /duplicate output/i) const escaped = manifestWith(opaqueResizeAsset()) escaped.assets[0].output = '../outside.png' assert.throws(() => validateAssetBuildManifest(escaped, workspace), /escapes workspace/i) const unknownField = manifestWith(opaqueResizeAsset()) unknownField.assets[0].logicalSlot = 'page' assert.throws(() => validateAssetBuildManifest(unknownField, workspace), /unknown field/i) const unknownMode = manifestWith(opaqueResizeAsset()) unknownMode.assets[0].processing.mode = 'future-magic' assert.throws(() => validateAssetBuildManifest(unknownMode, workspace), /unsupported processing\.mode/i) })