import { test } from 'node:test' import assert from 'node:assert/strict' import { createChunkTransferReport } from '../src/shared/chunk-transfer-report.ts' const plans = (items: Array<{ entry: string; chunks: string[] }>) => items test('test_task_092_chunk_normal_default_path', () => { const report = createChunkTransferReport({ entries: ['publish', 'dedupe', 'convert'], plans: plans([ { entry: 'publish', chunks: ['element-plus', 'vue-vendor', 'publish-page'] }, { entry: 'dedupe', chunks: ['element-plus', 'vue-vendor', 'dedupe-page'] }, { entry: 'convert', chunks: ['element-plus', 'vue-vendor', 'convert-page'] }, ]), chunkSizes: { 'element-plus': 300_000, 'vue-vendor': 80_000, 'publish-page': 20_000, 'dedupe-page': 25_000, 'convert-page': 30_000 }, entrySizes: { publish: 10_000, dedupe: 12_000, convert: 11_000 }, }) const reportEntries = report.entries assert.equal(reportEntries.length, 3) assert.deepEqual( reportEntries.map((e) => e.entry), ['publish', 'dedupe', 'convert'], '入口顺序稳定', ) assert.equal(reportEntries[0].entryBytes, 10_000) assert.equal(reportEntries[0].chunkBytes, 400_000) assert.equal(reportEntries[0].transferBytes, 410_000) assert.equal(reportEntries[0].chunkCount, 3) // 公共 chunk 只统计一次 assert.equal(report.sharedBytes, 380_000, 'element-plus + vue-vendor 合计') assert.equal(report.largest?.entry, 'convert') assert.equal(report.largest?.transferBytes, 421_000) assert.equal(report.smallest?.entry, 'publish') assert.equal(report.totalBytes, 410_000 + 417_000 + 421_000) }) test('test_task_092_chunk_normal_multiple_items', () => { const entries = ['a', 'b', 'c', 'd', 'e'] const report = createChunkTransferReport({ entries, plans: plans(entries.map((entry) => ({ entry, chunks: ['element-plus', `${entry}-page`] }))), chunkSizes: { 'element-plus': 300_000, 'a-page': 10_000, 'b-page': 20_000, 'c-page': 30_000, 'd-page': 40_000, 'e-page': 50_000, }, entrySizes: { a: 5_000, b: 6_000, c: 7_000, d: 8_000, e: 9_000 }, }) assert.equal(report.entries.length, 5) // 批量结果不丢失、顺序稳定 assert.deepEqual(report.entries.map((e) => e.entry), entries) for (let i = 0; i < entries.length; i++) { const item = report.entries[i] assert.equal(item.transferBytes, (i + 1) * 10_000 + 300_000 + (i + 5) * 1_000) } assert.equal(report.sharedBytes, 300_000, 'element-plus 出现在全部入口,只计一次') assert.deepEqual(report.entries.map((e) => e.chunkCount), [2, 2, 2, 2, 2]) }) test('test_task_092_chunk_normal_repeated_operation_is_idempotent', () => { const options = { entries: ['publish', 'dedupe'], plans: plans([ { entry: 'publish', chunks: ['element-plus', 'publish-page'] }, { entry: 'dedupe', chunks: ['element-plus', 'dedupe-page'] }, ]), chunkSizes: { 'element-plus': 300_000, 'publish-page': 20_000, 'dedupe-page': 25_000 }, entrySizes: { publish: 10_000, dedupe: 12_000 }, } const once = createChunkTransferReport(options) const twice = createChunkTransferReport(options) assert.deepEqual(once.entries, twice.entries, '同一输入两次计算结果一致') assert.equal(once.sharedBytes, twice.sharedBytes) assert.equal(once.totalBytes, twice.totalBytes) assert.deepEqual(once.largest, twice.largest) assert.deepEqual(once.smallest, twice.smallest) // 输入对象不被修改 assert.equal(options.chunkSizes['element-plus'], 300_000) assert.equal(options.plans[0].chunks.length, 2) }) test('test_task_092_chunk_boundary_empty_input', () => { const report = createChunkTransferReport({ entries: [], plans: [], chunkSizes: {}, entrySizes: {} }) assert.deepEqual(report.entries, []) assert.equal(report.sharedBytes, 0) assert.equal(report.totalBytes, 0) assert.equal(report.largest, undefined) assert.equal(report.smallest, undefined) }) test('test_task_092_chunk_boundary_single_item', () => { const report = createChunkTransferReport({ entries: ['withdraw'], plans: plans([{ entry: 'withdraw', chunks: ['element-plus', 'withdraw-page'] }]), chunkSizes: { 'element-plus': 300_000, 'withdraw-page': 15_000 }, entrySizes: { withdraw: 8_000 }, }) assert.equal(report.entries.length, 1) assert.equal(report.entries[0].transferBytes, 323_000) assert.equal(report.sharedBytes, 0, 'chunk 只出现在一个入口时不算公共') assert.equal(report.largest?.entry, 'withdraw') assert.equal(report.smallest?.entry, 'withdraw') assert.equal(report.largest?.transferBytes, report.smallest?.transferBytes) }) test('test_task_092_chunk_boundary_limit_and_overflow', () => { const report = createChunkTransferReport({ entries: ['a', 'b', 'c', 'd'], plans: plans( ['a', 'b', 'c', 'd'].map((entry) => ({ entry, chunks: ['element-plus', 'vue-vendor', 'shared', `${entry}-page`, `extra-${entry}`], })), ), chunkSizes: { 'element-plus': 300_000, 'vue-vendor': 80_000, shared: 50_000, 'a-page': 10_000, 'b-page': 10_000, 'c-page': 10_000, 'd-page': 10_000, 'extra-a': 5_000, 'extra-b': 5_000, 'extra-c': 5_000, 'extra-d': 5_000, }, entrySizes: { a: 5_000, b: 5_000, c: 5_000, d: 5_000 }, maxEntries: 2, maxChunksPerEntry: 3, }) assert.equal(report.entries.length, 2, '超过 maxEntries 只统计前 2 个入口') assert.deepEqual( report.entries.map((e) => e.entry), ['a', 'b'], ) assert.equal(report.entries[0].chunkCount, 3, '每入口最多统计 3 个 chunk') assert.equal(report.entries[0].chunkBytes, 430_000, 'element-plus + vue-vendor + shared') assert.equal(report.entries[0].transferBytes, 435_000) // 超出部分的 chunk 尺寸不计入 assert.ok(!('extra-a' in report.chunkBytesOf(report.entries[0])), '被截断的 chunk 不计入') // 入口缺失 plan 或尺寸:fail-fast 抛错 assert.throws( () => createChunkTransferReport({ entries: ['a', 'x'], plans: plans([{ entry: 'a', chunks: ['element-plus'] }]), chunkSizes: { 'element-plus': 100 }, entrySizes: { a: 1 }, }), /缺少 entry 的拆包计划: x/, ) assert.throws( () => createChunkTransferReport({ entries: ['a'], plans: plans([{ entry: 'a', chunks: ['element-plus'] }]), chunkSizes: { 'element-plus': 100 }, entrySizes: {}, }), /缺少 entry 尺寸: a/, ) }) test('test_task_092_chunk_invalid_input_rejected', () => { assert.throws(() => createChunkTransferReport({} as never), /entries 必须是数组/) assert.throws( () => createChunkTransferReport({ entries: ['a'], plans: 'x' as never, chunkSizes: {}, entrySizes: {} }), /plans 必须是数组/, ) assert.throws( () => createChunkTransferReport({ entries: ['a'], plans: [], chunkSizes: null as never, entrySizes: {} }), /chunkSizes 必须是对象/, ) assert.throws( () => createChunkTransferReport({ entries: ['a'], plans: [], chunkSizes: {}, entrySizes: null as never }), /entrySizes 必须是对象/, ) assert.throws( () => createChunkTransferReport({ entries: ['a'], plans: [], chunkSizes: {}, entrySizes: new Date() as never }), /entrySizes 必须是对象/, ) assert.throws( () => createChunkTransferReport({ entries: ['a'], plans: [], chunkSizes: {}, entrySizes: {}, maxEntries: 0 }), /maxEntries 必须为正数/, ) assert.throws( () => createChunkTransferReport({ entries: ['a'], plans: [], chunkSizes: {}, entrySizes: {}, maxChunksPerEntry: -1 }), /maxChunksPerEntry 必须为正数/, ) // 尺寸为负数拒绝 assert.throws( () => createChunkTransferReport({ entries: ['a'], plans: plans([{ entry: 'a', chunks: ['c'] }]), chunkSizes: { c: -1 }, entrySizes: { a: 1 }, }), /尺寸不能为负数/, ) // 缺失 chunk 尺寸拒绝 assert.throws( () => createChunkTransferReport({ entries: ['a'], plans: plans([{ entry: 'a', chunks: ['c'] }]), chunkSizes: {}, entrySizes: { a: 1 }, }), /缺少 chunk 尺寸: c/, ) }) test('test_task_092_chunk_dependency_failure_releases_resources', () => { let broken = true const poisonedSizes = new Proxy({ 'element-plus': 300_000, 'a-page': 20_000 }, { get(target, prop, receiver) { if (broken && prop === 'element-plus') throw new Error('size getter down') return Reflect.get(target, prop, receiver) }, }) const options = { entries: ['a'], plans: plans([{ entry: 'a', chunks: ['element-plus', 'a-page'] }]), chunkSizes: poisonedSizes as Record, entrySizes: { a: 5_000 }, } // 依赖(尺寸读取)失败:计算抛错,不产生部分结果 assert.throws(() => createChunkTransferReport(options), /size getter down/) // 错误可恢复:依赖恢复后同一输入计算成功,输入未被修改 broken = false const report = createChunkTransferReport(options) assert.equal(report.entries.length, 1) assert.equal(report.entries[0].transferBytes, 325_000) assert.deepEqual(options.plans[0].chunks, ['element-plus', 'a-page']) })