import { describe, expect, test } from 'vitest'; import { currentVersionResourceBindingIds, formatIterationVersionLabel, isResourceUsedByCurrentVersion, } from '../src/features/resource-canvas/resourceCanvasVersionBindingModel'; /** * 夹具必须与落盘口径一致:`versions[].createdAt` 是 **Unix 秒**。 * * 写入侧见 `project/manifest.rs` 的 `created_at: unix_timestamp()`(`as_secs()`)。 * `1788075047` 取自真机项目 `What do u wanna do kitten` 的 `versions[0].createdAt`, * 它被当成毫秒渲染时正好是 `1970/1/22 00:41:15`,即真机截图里的 1970 年。 */ const versions = [ { versionId: 'version-root', parentVersionId: null, projectRevision: 3, resourceBindings: [ { slotId: 'asset:asset-player', resourceId: 'asset-player' }, ], createdReason: 'initial' as const, createdAt: 1_788_075_047, }, { versionId: 'version-child', parentVersionId: 'version-root', projectRevision: 4, resourceBindings: [ { slotId: 'asset:asset-town', resourceId: 'asset-town' }, // 悬空绑定的 slotId 不是恒等映射,不参与「当前使用」判定。 { slotId: 'player', resourceId: 'asset-player' }, ], createdReason: 'agent-revision' as const, createdAt: 1_788_075_104, }, ]; describe('运行模块版本绑定判定', () => { test('falls back to the newest manifest version when no version is selected', () => { expect(Array.from(currentVersionResourceBindingIds({ versions }))).toEqual([ 'asset-town', ]); }); test('resolves the binding ids of the explicitly selected version', () => { expect( Array.from( currentVersionResourceBindingIds({ versions }, 'version-root'), ), ).toEqual(['asset-player']); }); test('returns no bindings for a missing version or a manifest without versions', () => { expect( currentVersionResourceBindingIds({ versions }, 'version-missing').size, ).toBe(0); expect(currentVersionResourceBindingIds({}).size).toBe(0); expect(currentVersionResourceBindingIds({ versions: [] }).size).toBe(0); }); test('marks only registered assets bound by the current version as in use', () => { const bindingIds = currentVersionResourceBindingIds( { versions }, 'version-root', ); expect( isResourceUsedByCurrentVersion( { manifestAssetId: 'asset-player' }, bindingIds, ), ).toBe(true); expect( isResourceUsedByCurrentVersion( { manifestAssetId: 'asset-town' }, bindingIds, ), ).toBe(false); // 未登记为 manifest 素材的资源永远不算「当前使用」。 expect( isResourceUsedByCurrentVersion({ manifestAssetId: null }, bindingIds), ).toBe(false); }); test('labels a version with its Chinese created reason and creation time', () => { expect(formatIterationVersionLabel(versions[0])).toBe( `初始版本 · ${new Date(1_788_075_047 * 1000).toLocaleString('zh-CN')}`, ); expect(formatIterationVersionLabel(versions[1])).toBe( `智能体修订 · ${new Date(1_788_075_104 * 1000).toLocaleString('zh-CN')}`, ); }); test('把版本 createdAt 的 Unix 秒换算成毫秒,不再渲染成 1970 年', () => { // 期望值按同一个本地时区口径算出,用例结果不随运行机器时区漂移。 // 1788075047 秒 = 2026-08-30T07:30:47Z。 const createdAtLocal = new Date(1_788_075_047 * 1000).toLocaleString( 'zh-CN', ); const label = formatIterationVersionLabel(versions[0]); expect(label).toBe(`初始版本 · ${createdAtLocal}`); // 年 / 月 / 日必须来自真实时刻,而不是 1970。这里用显式数值选项断言, // 不依赖 `toLocaleString` 的具体排版(不同 ICU 版本会给出不同分隔符)。 expect( new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'numeric', day: 'numeric', }).format(new Date(1_788_075_047 * 1000)), ).toBe('2026/8/30'); // 回归守卫:按毫秒解释同一个值会落到 1970/1/22,这正是真机截图里的错误显示。 expect(label).not.toContain('1970'); }); });