diff --git a/apps/azure/src/components/editor/FrameView.tsx b/apps/azure/src/components/editor/FrameView.tsx index 9092f2c10..0d6c01431 100644 --- a/apps/azure/src/components/editor/FrameView.tsx +++ b/apps/azure/src/components/editor/FrameView.tsx @@ -13,18 +13,7 @@ import React from 'react'; import { LayeredProcessView } from '@variscout/ui'; import { useProjectStore } from '@variscout/stores'; import type { ProcessContext } from '@variscout/core'; -import { detectGaps, type ProcessMap } from '@variscout/core/frame'; - -const createEmptyMap = (): ProcessMap => { - const now = new Date().toISOString(); - return { - version: 1, - nodes: [], - tributaries: [], - createdAt: now, - updatedAt: now, - }; -}; +import { createEmptyMap, detectGaps, type ProcessMap } from '@variscout/core/frame'; const FrameView: React.FC = () => { const rawData = useProjectStore(s => s.rawData); diff --git a/apps/pwa/src/components/views/FrameView.tsx b/apps/pwa/src/components/views/FrameView.tsx index 1c06966c9..8214de26b 100644 --- a/apps/pwa/src/components/views/FrameView.tsx +++ b/apps/pwa/src/components/views/FrameView.tsx @@ -10,18 +10,7 @@ import React from 'react'; import { LayeredProcessView } from '@variscout/ui'; import { useProjectStore } from '@variscout/stores'; import type { ProcessContext } from '@variscout/core'; -import { detectGaps, type ProcessMap } from '@variscout/core/frame'; - -const createEmptyMap = (): ProcessMap => { - const now = new Date().toISOString(); - return { - version: 1, - nodes: [], - tributaries: [], - createdAt: now, - updatedAt: now, - }; -}; +import { createEmptyMap, detectGaps, type ProcessMap } from '@variscout/core/frame'; const FrameView: React.FC = () => { const rawData = useProjectStore(s => s.rawData); diff --git a/packages/core/src/frame/__tests__/factories.test.ts b/packages/core/src/frame/__tests__/factories.test.ts new file mode 100644 index 000000000..47079bcc5 --- /dev/null +++ b/packages/core/src/frame/__tests__/factories.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; +import { createEmptyMap } from '../factories'; + +describe('createEmptyMap', () => { + it('returns a v1 ProcessMap with no nodes or tributaries', () => { + const map = createEmptyMap(); + expect(map.version).toBe(1); + expect(map.nodes).toEqual([]); + expect(map.tributaries).toEqual([]); + }); + + it('stamps createdAt and updatedAt with the same ISO timestamp', () => { + const map = createEmptyMap(); + expect(map.createdAt).toBe(map.updatedAt); + expect(() => new Date(map.createdAt).toISOString()).not.toThrow(); + }); + + it('produces independent instances on each call', () => { + const a = createEmptyMap(); + const b = createEmptyMap(); + expect(a).not.toBe(b); + expect(a.nodes).not.toBe(b.nodes); + expect(a.tributaries).not.toBe(b.tributaries); + }); +}); diff --git a/packages/core/src/frame/factories.ts b/packages/core/src/frame/factories.ts new file mode 100644 index 000000000..1415e482d --- /dev/null +++ b/packages/core/src/frame/factories.ts @@ -0,0 +1,16 @@ +import type { ProcessMap } from './types'; + +/** + * Build an empty `ProcessMap` with current timestamps. Used by FRAME views in + * both apps as the initial state when no `processContext.processMap` exists. + */ +export function createEmptyMap(): ProcessMap { + const now = new Date().toISOString(); + return { + version: 1, + nodes: [], + tributaries: [], + createdAt: now, + updatedAt: now, + }; +} diff --git a/packages/core/src/frame/index.ts b/packages/core/src/frame/index.ts index 48f607b2b..285a77d2c 100644 --- a/packages/core/src/frame/index.ts +++ b/packages/core/src/frame/index.ts @@ -23,3 +23,4 @@ export type { export { inferMode } from './modeInference'; export { detectGaps } from './gapDetector'; export { subgroupAxisColumns } from './subgroupAxes'; +export { createEmptyMap } from './factories';