Files
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

621 lines
25 KiB
TypeScript

/**
* @vitest-environment node
*
* Tests for the per-workflow execution store.
*
* These tests cover:
* - Default state for unknown workflows
* - Per-workflow state isolation
* - Execution lifecycle (start/stop clears run path)
* - Block and edge run status tracking
* - Active block management
* - The {@link ExecutionStatus} enum and its derived `isExecuting` /
* `isDebugging` booleans (exhaustive status → flag mapping + transitions)
* - Execution snapshot management
* - Store reset
* - Immutability guarantees
*
* @remarks
* The store under test transitively imports the workflow registry store,
* which drags in the block registry and emcn icon CSS. To keep this a true
* unit test that loads under the node environment, the registry store is
* mocked to a minimal stub (the store actions never touch it — only the
* convenience hooks do, which are not exercised here).
*
* Most tests use `it.concurrent` with unique workflow IDs per test.
* Because the store isolates state by workflow ID, concurrent tests
* do not interfere with each other. The `reset` and `immutability`
* groups run sequentially since they affect or read global store state.
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('@/stores/workflows/registry/store', () => ({
useWorkflowRegistry: Object.assign(
vi.fn(() => null),
{ getState: vi.fn(() => ({ activeWorkflowId: null })) }
),
}))
vi.unmock('@/stores/execution/store')
vi.unmock('@/stores/execution/types')
import { useExecutionStore } from '@/stores/execution/store'
import {
defaultWorkflowExecutionState,
deriveExecutionFlags,
type ExecutionStatus,
initialState,
} from '@/stores/execution/types'
describe('useExecutionStore', () => {
describe('getWorkflowExecution', () => {
it.concurrent('should return default state for an unknown workflow', () => {
const state = useExecutionStore.getState().getWorkflowExecution('wf-get-default')
expect(state.status).toBe('idle')
expect(state.isExecuting).toBe(false)
expect(state.isDebugging).toBe(false)
expect(state.activeBlockIds.size).toBe(0)
expect(state.pendingBlocks).toEqual([])
expect(state.executor).toBeNull()
expect(state.debugContext).toBeNull()
expect(state.lastRunPath.size).toBe(0)
expect(state.lastRunEdges.size).toBe(0)
})
it.concurrent(
'should return fresh collections for unknown workflows, not shared references',
() => {
const stateA = useExecutionStore.getState().getWorkflowExecution('wf-fresh-a')
const stateB = useExecutionStore.getState().getWorkflowExecution('wf-fresh-b')
expect(stateA.activeBlockIds).not.toBe(stateB.activeBlockIds)
expect(stateA.lastRunPath).not.toBe(stateB.lastRunPath)
expect(stateA.lastRunEdges).not.toBe(stateB.lastRunEdges)
expect(stateA.activeBlockIds).not.toBe(defaultWorkflowExecutionState.activeBlockIds)
}
)
it.concurrent('should return the stored state after a mutation', () => {
useExecutionStore.getState().setIsExecuting('wf-get-stored', true)
const state = useExecutionStore.getState().getWorkflowExecution('wf-get-stored')
expect(state.isExecuting).toBe(true)
})
})
describe('deriveExecutionFlags', () => {
it.concurrent('maps every status to the documented legacy booleans', () => {
const cases: Array<[ExecutionStatus, boolean, boolean]> = [
['idle', false, false],
['running', true, false],
['debugging', true, true],
]
for (const [status, isExecuting, isDebugging] of cases) {
expect(deriveExecutionFlags(status)).toEqual({ isExecuting, isDebugging })
}
})
})
describe('setIsExecuting', () => {
it.concurrent('should set isExecuting to true (status running)', () => {
useExecutionStore.getState().setIsExecuting('wf-exec-true', true)
const state = useExecutionStore.getState().getWorkflowExecution('wf-exec-true')
expect(state.isExecuting).toBe(true)
expect(state.status).toBe('running')
})
it.concurrent('should set isExecuting to false (status idle)', () => {
useExecutionStore.getState().setIsExecuting('wf-exec-false', true)
useExecutionStore.getState().setIsExecuting('wf-exec-false', false)
const state = useExecutionStore.getState().getWorkflowExecution('wf-exec-false')
expect(state.isExecuting).toBe(false)
expect(state.status).toBe('idle')
})
it.concurrent('should clear lastRunPath and lastRunEdges when starting execution', () => {
const wf = 'wf-exec-clears-run'
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
useExecutionStore.getState().setEdgeRunStatus(wf, 'edge-1', 'success')
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath.size).toBe(1)
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunEdges.size).toBe(1)
useExecutionStore.getState().setIsExecuting(wf, true)
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.lastRunPath.size).toBe(0)
expect(state.lastRunEdges.size).toBe(0)
expect(state.isExecuting).toBe(true)
})
it.concurrent('should NOT clear lastRunPath when stopping execution', () => {
const wf = 'wf-exec-stop-keeps-path'
useExecutionStore.getState().setIsExecuting(wf, true)
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
useExecutionStore.getState().setIsExecuting(wf, false)
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.isExecuting).toBe(false)
expect(state.lastRunPath.get('block-1')).toBe('success')
})
it.concurrent('starting a debug run then setIsExecuting(true) clears the run path', () => {
const wf = 'wf-exec-debug-start-clears'
useExecutionStore.getState().setIsExecuting(wf, true)
useExecutionStore.getState().setIsDebugging(wf, true)
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
useExecutionStore.getState().setIsExecuting(wf, true)
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.status).toBe('debugging')
expect(state.isExecuting).toBe(true)
expect(state.isDebugging).toBe(true)
expect(state.lastRunPath.size).toBe(0)
expect(state.lastRunEdges.size).toBe(0)
})
})
describe('setIsDebugging', () => {
it.concurrent('should toggle debug mode', () => {
const wf = 'wf-debug-toggle'
useExecutionStore.getState().setIsDebugging(wf, true)
expect(useExecutionStore.getState().getWorkflowExecution(wf).isDebugging).toBe(true)
expect(useExecutionStore.getState().getWorkflowExecution(wf).isExecuting).toBe(true)
expect(useExecutionStore.getState().getWorkflowExecution(wf).status).toBe('debugging')
useExecutionStore.getState().setIsDebugging(wf, false)
expect(useExecutionStore.getState().getWorkflowExecution(wf).isDebugging).toBe(false)
expect(useExecutionStore.getState().getWorkflowExecution(wf).isExecuting).toBe(true)
expect(useExecutionStore.getState().getWorkflowExecution(wf).status).toBe('running')
})
it.concurrent('setIsDebugging(false) while idle is a no-op (stays idle)', () => {
const wf = 'wf-debug-false-idle'
useExecutionStore.getState().setIsDebugging(wf, false)
expect(useExecutionStore.getState().getWorkflowExecution(wf).status).toBe('idle')
expect(useExecutionStore.getState().getWorkflowExecution(wf).isExecuting).toBe(false)
})
it.concurrent('setIsDebugging(false) while running keeps running', () => {
const wf = 'wf-debug-false-running'
useExecutionStore.getState().setIsExecuting(wf, true)
useExecutionStore.getState().setIsDebugging(wf, false)
expect(useExecutionStore.getState().getWorkflowExecution(wf).status).toBe('running')
expect(useExecutionStore.getState().getWorkflowExecution(wf).isExecuting).toBe(true)
})
it.concurrent('does not clear the run path when entering debug mode', () => {
const wf = 'wf-debug-keeps-path'
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
useExecutionStore.getState().setIsDebugging(wf, true)
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath.get('block-1')).toBe(
'success'
)
})
})
describe('status enum', () => {
it.concurrent('idle derives both flags false', () => {
const wf = 'wf-status-idle'
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.status).toBe('idle')
expect(state.isExecuting).toBe(false)
expect(state.isDebugging).toBe(false)
})
it.concurrent('running derives isExecuting only', () => {
const wf = 'wf-status-running'
useExecutionStore.getState().setStatus(wf, 'running')
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.status).toBe('running')
expect(state.isExecuting).toBe(true)
expect(state.isDebugging).toBe(false)
})
it.concurrent('debugging derives both flags true', () => {
const wf = 'wf-status-debugging'
useExecutionStore.getState().setStatus(wf, 'debugging')
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.status).toBe('debugging')
expect(state.isExecuting).toBe(true)
expect(state.isDebugging).toBe(true)
})
it.concurrent('setStatus preserves the run path unless clearRunPath is passed', () => {
const wf = 'wf-status-path-rules'
useExecutionStore.getState().setStatus(wf, 'debugging')
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath.size).toBe(1)
useExecutionStore.getState().setStatus(wf, 'running')
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath.size).toBe(1)
useExecutionStore.getState().setStatus(wf, 'running', { clearRunPath: true })
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath.size).toBe(0)
})
it.concurrent('the derived booleans always agree with the stored status', () => {
const wf = 'wf-status-no-drift'
for (const status of ['idle', 'running', 'debugging', 'idle'] as const) {
useExecutionStore.getState().setStatus(wf, status)
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect({ isExecuting: state.isExecuting, isDebugging: state.isDebugging }).toEqual(
deriveExecutionFlags(status)
)
}
})
it.concurrent('setIsExecuting(true) preserves an active debug session', () => {
const wf = 'wf-status-debug-preserve'
useExecutionStore.getState().setStatus(wf, 'debugging')
useExecutionStore.getState().setIsExecuting(wf, true)
expect(useExecutionStore.getState().getWorkflowExecution(wf).status).toBe('debugging')
})
it.concurrent('setIsExecuting(false) returns to idle from any mode', () => {
const wf = 'wf-status-stop'
useExecutionStore.getState().setStatus(wf, 'debugging')
useExecutionStore.getState().setIsExecuting(wf, false)
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.status).toBe('idle')
expect(state.isExecuting).toBe(false)
expect(state.isDebugging).toBe(false)
})
})
describe('setActiveBlocks', () => {
it.concurrent('should set the active block IDs', () => {
const wf = 'wf-active-set'
useExecutionStore.getState().setActiveBlocks(wf, new Set(['block-1', 'block-2']))
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.activeBlockIds.has('block-1')).toBe(true)
expect(state.activeBlockIds.has('block-2')).toBe(true)
expect(state.activeBlockIds.size).toBe(2)
})
it.concurrent('should replace the previous set', () => {
const wf = 'wf-active-replace'
useExecutionStore.getState().setActiveBlocks(wf, new Set(['block-1']))
useExecutionStore.getState().setActiveBlocks(wf, new Set(['block-2']))
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.activeBlockIds.has('block-1')).toBe(false)
expect(state.activeBlockIds.has('block-2')).toBe(true)
})
it.concurrent('should clear active blocks with an empty set', () => {
const wf = 'wf-active-clear'
useExecutionStore.getState().setActiveBlocks(wf, new Set(['block-1']))
useExecutionStore.getState().setActiveBlocks(wf, new Set())
expect(useExecutionStore.getState().getWorkflowExecution(wf).activeBlockIds.size).toBe(0)
})
})
describe('setPendingBlocks', () => {
it.concurrent('should set pending block IDs', () => {
const wf = 'wf-pending'
useExecutionStore.getState().setPendingBlocks(wf, ['block-1', 'block-2'])
expect(useExecutionStore.getState().getWorkflowExecution(wf).pendingBlocks).toEqual([
'block-1',
'block-2',
])
})
})
describe('setExecutor', () => {
it.concurrent('should store and clear executor', () => {
const wf = 'wf-executor'
const mockExecutor = { run: () => {} } as any
useExecutionStore.getState().setExecutor(wf, mockExecutor)
expect(useExecutionStore.getState().getWorkflowExecution(wf).executor).toBe(mockExecutor)
useExecutionStore.getState().setExecutor(wf, null)
expect(useExecutionStore.getState().getWorkflowExecution(wf).executor).toBeNull()
})
})
describe('setDebugContext', () => {
it.concurrent('should store and clear debug context', () => {
const wf = 'wf-debug-ctx'
const mockContext = { blockId: 'block-1' } as any
useExecutionStore.getState().setDebugContext(wf, mockContext)
expect(useExecutionStore.getState().getWorkflowExecution(wf).debugContext).toBe(mockContext)
useExecutionStore.getState().setDebugContext(wf, null)
expect(useExecutionStore.getState().getWorkflowExecution(wf).debugContext).toBeNull()
})
})
describe('setBlockRunStatus', () => {
it.concurrent('should record a success status for a block', () => {
const wf = 'wf-block-success'
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath.get('block-1')).toBe(
'success'
)
})
it.concurrent('should record an error status for a block', () => {
const wf = 'wf-block-error'
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'error')
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath.get('block-1')).toBe(
'error'
)
})
it.concurrent('should accumulate statuses for multiple blocks', () => {
const wf = 'wf-block-accum'
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
useExecutionStore.getState().setBlockRunStatus(wf, 'block-2', 'error')
const runPath = useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath
expect(runPath.get('block-1')).toBe('success')
expect(runPath.get('block-2')).toBe('error')
expect(runPath.size).toBe(2)
})
it.concurrent('should overwrite a previous status for the same block', () => {
const wf = 'wf-block-overwrite'
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'error')
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath.get('block-1')).toBe(
'success'
)
})
})
describe('setEdgeRunStatus', () => {
it.concurrent('should record a success status for an edge', () => {
const wf = 'wf-edge-success'
useExecutionStore.getState().setEdgeRunStatus(wf, 'edge-1', 'success')
expect(useExecutionStore.getState().getWorkflowExecution(wf).lastRunEdges.get('edge-1')).toBe(
'success'
)
})
it.concurrent('should accumulate statuses for multiple edges', () => {
const wf = 'wf-edge-accum'
useExecutionStore.getState().setEdgeRunStatus(wf, 'edge-1', 'success')
useExecutionStore.getState().setEdgeRunStatus(wf, 'edge-2', 'error')
const runEdges = useExecutionStore.getState().getWorkflowExecution(wf).lastRunEdges
expect(runEdges.get('edge-1')).toBe('success')
expect(runEdges.get('edge-2')).toBe('error')
expect(runEdges.size).toBe(2)
})
})
describe('clearRunPath', () => {
it.concurrent('should clear both lastRunPath and lastRunEdges', () => {
const wf = 'wf-clear-both'
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
useExecutionStore.getState().setEdgeRunStatus(wf, 'edge-1', 'success')
useExecutionStore.getState().clearRunPath(wf)
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.lastRunPath.size).toBe(0)
expect(state.lastRunEdges.size).toBe(0)
})
it.concurrent('should not affect other workflow state', () => {
const wf = 'wf-clear-other'
useExecutionStore.getState().setIsExecuting(wf, true)
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
useExecutionStore.getState().clearRunPath(wf)
const state = useExecutionStore.getState().getWorkflowExecution(wf)
expect(state.isExecuting).toBe(true)
expect(state.lastRunPath.size).toBe(0)
})
})
describe('per-workflow isolation', () => {
it.concurrent('should keep execution state independent between workflows', () => {
const wfA = 'wf-iso-exec-a'
const wfB = 'wf-iso-exec-b'
useExecutionStore.getState().setIsExecuting(wfA, true)
useExecutionStore.getState().setActiveBlocks(wfA, new Set(['block-a1']))
useExecutionStore.getState().setIsExecuting(wfB, false)
useExecutionStore.getState().setActiveBlocks(wfB, new Set(['block-b1', 'block-b2']))
const stateA = useExecutionStore.getState().getWorkflowExecution(wfA)
const stateB = useExecutionStore.getState().getWorkflowExecution(wfB)
expect(stateA.isExecuting).toBe(true)
expect(stateA.activeBlockIds.size).toBe(1)
expect(stateA.activeBlockIds.has('block-a1')).toBe(true)
expect(stateB.isExecuting).toBe(false)
expect(stateB.activeBlockIds.size).toBe(2)
expect(stateB.activeBlockIds.has('block-b1')).toBe(true)
})
it.concurrent('should keep run path independent between workflows', () => {
const wfA = 'wf-iso-path-a'
const wfB = 'wf-iso-path-b'
useExecutionStore.getState().setBlockRunStatus(wfA, 'block-1', 'success')
useExecutionStore.getState().setEdgeRunStatus(wfA, 'edge-1', 'success')
useExecutionStore.getState().setBlockRunStatus(wfB, 'block-1', 'error')
useExecutionStore.getState().setEdgeRunStatus(wfB, 'edge-1', 'error')
const stateA = useExecutionStore.getState().getWorkflowExecution(wfA)
const stateB = useExecutionStore.getState().getWorkflowExecution(wfB)
expect(stateA.lastRunPath.get('block-1')).toBe('success')
expect(stateA.lastRunEdges.get('edge-1')).toBe('success')
expect(stateB.lastRunPath.get('block-1')).toBe('error')
expect(stateB.lastRunEdges.get('edge-1')).toBe('error')
})
it.concurrent('should not affect workflow B when starting execution on workflow A', () => {
const wfA = 'wf-iso-start-a'
const wfB = 'wf-iso-start-b'
useExecutionStore.getState().setBlockRunStatus(wfA, 'block-1', 'success')
useExecutionStore.getState().setBlockRunStatus(wfB, 'block-1', 'success')
useExecutionStore.getState().setIsExecuting(wfA, true)
const stateA = useExecutionStore.getState().getWorkflowExecution(wfA)
const stateB = useExecutionStore.getState().getWorkflowExecution(wfB)
expect(stateA.lastRunPath.size).toBe(0)
expect(stateB.lastRunPath.get('block-1')).toBe('success')
})
it.concurrent('should not affect workflow B when clearing run path on workflow A', () => {
const wfA = 'wf-iso-clear-a'
const wfB = 'wf-iso-clear-b'
useExecutionStore.getState().setBlockRunStatus(wfA, 'block-1', 'success')
useExecutionStore.getState().setBlockRunStatus(wfB, 'block-2', 'error')
useExecutionStore.getState().clearRunPath(wfA)
expect(useExecutionStore.getState().getWorkflowExecution(wfA).lastRunPath.size).toBe(0)
expect(
useExecutionStore.getState().getWorkflowExecution(wfB).lastRunPath.get('block-2')
).toBe('error')
})
})
describe('execution snapshots', () => {
const mockSnapshot = {
blockStates: {},
blockLogs: [],
executionOrder: [],
} as any
it.concurrent('should store a snapshot', () => {
const wf = 'wf-snap-store'
useExecutionStore.getState().setLastExecutionSnapshot(wf, mockSnapshot)
expect(useExecutionStore.getState().getLastExecutionSnapshot(wf)).toBe(mockSnapshot)
})
it.concurrent('should return undefined for unknown workflows', () => {
expect(
useExecutionStore.getState().getLastExecutionSnapshot('wf-snap-unknown')
).toBeUndefined()
})
it.concurrent('should clear a snapshot', () => {
const wf = 'wf-snap-clear'
useExecutionStore.getState().setLastExecutionSnapshot(wf, mockSnapshot)
useExecutionStore.getState().clearLastExecutionSnapshot(wf)
expect(useExecutionStore.getState().getLastExecutionSnapshot(wf)).toBeUndefined()
})
it.concurrent('should keep snapshots independent between workflows', () => {
const wfA = 'wf-snap-iso-a'
const wfB = 'wf-snap-iso-b'
const snapshotB = { blockStates: { x: 1 } } as any
useExecutionStore.getState().setLastExecutionSnapshot(wfA, mockSnapshot)
useExecutionStore.getState().setLastExecutionSnapshot(wfB, snapshotB)
expect(useExecutionStore.getState().getLastExecutionSnapshot(wfA)).toBe(mockSnapshot)
expect(useExecutionStore.getState().getLastExecutionSnapshot(wfB)).toBe(snapshotB)
})
})
describe('reset', () => {
beforeEach(() => {
useExecutionStore.setState(initialState)
})
it('should clear all workflow execution state', () => {
useExecutionStore.getState().setIsExecuting('wf-reset-a', true)
useExecutionStore.getState().setBlockRunStatus('wf-reset-a', 'block-1', 'success')
useExecutionStore.getState().setLastExecutionSnapshot('wf-reset-a', {} as any)
useExecutionStore.getState().reset()
const state = useExecutionStore.getState()
expect(state.workflowExecutions.size).toBe(0)
expect(state.lastExecutionSnapshots.size).toBe(0)
})
it('should return defaults for all workflows after reset', () => {
useExecutionStore.getState().setIsExecuting('wf-reset-b', true)
useExecutionStore.getState().setIsExecuting('wf-reset-c', true)
useExecutionStore.getState().reset()
expect(useExecutionStore.getState().getWorkflowExecution('wf-reset-b').isExecuting).toBe(
false
)
expect(useExecutionStore.getState().getWorkflowExecution('wf-reset-c').isExecuting).toBe(
false
)
})
})
describe('immutability', () => {
beforeEach(() => {
useExecutionStore.setState(initialState)
})
it('should create a new workflowExecutions map on each mutation', () => {
const mapBefore = useExecutionStore.getState().workflowExecutions
useExecutionStore.getState().setIsExecuting('wf-immut-map', true)
const mapAfter = useExecutionStore.getState().workflowExecutions
expect(mapBefore).not.toBe(mapAfter)
})
it('should create a new lastRunPath map when adding block status', () => {
const wf = 'wf-immut-path'
useExecutionStore.getState().setBlockRunStatus(wf, 'block-1', 'success')
const pathBefore = useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath
useExecutionStore.getState().setBlockRunStatus(wf, 'block-2', 'error')
const pathAfter = useExecutionStore.getState().getWorkflowExecution(wf).lastRunPath
expect(pathBefore).not.toBe(pathAfter)
expect(pathBefore.size).toBe(1)
expect(pathAfter.size).toBe(2)
})
it('should create a new lastRunEdges map when adding edge status', () => {
const wf = 'wf-immut-edges'
useExecutionStore.getState().setEdgeRunStatus(wf, 'edge-1', 'success')
const edgesBefore = useExecutionStore.getState().getWorkflowExecution(wf).lastRunEdges
useExecutionStore.getState().setEdgeRunStatus(wf, 'edge-2', 'error')
const edgesAfter = useExecutionStore.getState().getWorkflowExecution(wf).lastRunEdges
expect(edgesBefore).not.toBe(edgesAfter)
expect(edgesBefore.size).toBe(1)
expect(edgesAfter.size).toBe(2)
})
it.concurrent('should not mutate the default state constant', () => {
useExecutionStore.getState().setBlockRunStatus('wf-immut-const', 'block-1', 'success')
expect(defaultWorkflowExecutionState.lastRunPath.size).toBe(0)
expect(defaultWorkflowExecutionState.lastRunEdges.size).toBe(0)
expect(defaultWorkflowExecutionState.activeBlockIds.size).toBe(0)
})
})
})