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

229 lines
7.5 KiB
TypeScript

/**
* @vitest-environment node
*
* Tests for the workflow-diff store's status modeling.
*
* Focus: the {@link WorkflowDiffStatus} enum is the single source of truth and
* the legacy `hasActiveDiff` / `isShowingDiff` / `isDiffReady` booleans are
* derived from it, so contradictory combinations are unrepresentable. We assert
* the exhaustive status → boolean mapping and the status transitions driven by
* the tractable actions (`toggleDiffView`, `clearDiff`, `_batchedStateUpdate`).
*
* @remarks
* The store transitively imports the diff engine, serializer, socket
* operations, and the workflow/registry stores, all of which drag in the block
* registry and emcn icon CSS. Every such dependency is mocked so the suite
* loads under the node environment and exercises only the store + its types.
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { applyWorkflowStateToStores } = vi.hoisted(() => ({
applyWorkflowStateToStores: vi.fn(),
}))
vi.mock('@sim/logger', () => ({
createLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }),
}))
vi.mock('@/lib/workflows/diff', () => ({
WorkflowDiffEngine: class {
clearDiff = vi.fn()
createDiffFromWorkflowState = vi.fn()
},
stripWorkflowDiffMarkers: vi.fn((s) => s),
}))
vi.mock('@/lib/workflows/operations/socket-operations', () => ({
enqueueReplaceWorkflowState: vi.fn().mockResolvedValue(undefined),
}))
vi.mock('@/lib/workflows/sanitization/validation', () => ({
validateWorkflowState: vi.fn(() => ({ valid: true, errors: [], sanitizedState: null })),
}))
vi.mock('@/serializer', () => ({
Serializer: class {
serializeWorkflow = vi.fn()
deserializeWorkflow = vi.fn()
},
}))
vi.mock('@/stores/workflows/registry/store', () => ({
useWorkflowRegistry: { getState: vi.fn(() => ({ activeWorkflowId: null })) },
}))
vi.mock('@/stores/workflows/utils', () => ({
mergeSubblockState: vi.fn((blocks) => blocks),
}))
vi.mock('@/stores/workflows/workflow/store', () => ({
useWorkflowStore: {
getState: vi.fn(() => ({
getWorkflowState: vi.fn(() => ({ blocks: {}, edges: [], loops: {}, parallels: {} })),
blocks: {},
lastSaved: 0,
})),
setState: vi.fn(),
},
}))
vi.mock('@/stores/workflow-diff/utils', () => ({
applyWorkflowStateToStores,
captureBaselineSnapshot: vi.fn(),
cloneWorkflowState: vi.fn((s) => s),
createBatchedUpdater:
(set: (u: Record<string, unknown>) => void) => (updates: Record<string, unknown>) =>
set(updates),
getLatestUserMessageId: vi.fn().mockResolvedValue(null),
persistWorkflowStateToServer: vi.fn().mockResolvedValue(true),
WORKFLOW_DIFF_SETTLED_EVENT: 'workflow-diff-settled',
}))
import { RESET_DIFF_STATE, useWorkflowDiffStore } from '@/stores/workflow-diff/store'
import {
deriveDiffFlags,
type WorkflowDiffState,
type WorkflowDiffStatus,
} from '@/stores/workflow-diff/types'
function seedStatus(status: WorkflowDiffStatus): void {
useWorkflowDiffStore.setState(deriveDiffFlags(status))
}
describe('useWorkflowDiffStore status modeling', () => {
beforeEach(() => {
vi.clearAllMocks()
useWorkflowDiffStore.setState({
...RESET_DIFF_STATE,
pendingExternalUpdates: {},
remoteUpdateVersions: {},
reconcilingWorkflows: {},
reconciliationErrors: {},
} as Partial<WorkflowDiffState>)
})
describe('deriveDiffFlags', () => {
it('maps every status to the documented legacy booleans', () => {
expect(deriveDiffFlags('none')).toEqual({
status: 'none',
hasActiveDiff: false,
isShowingDiff: false,
isDiffReady: false,
})
expect(deriveDiffFlags('staged')).toEqual({
status: 'staged',
hasActiveDiff: true,
isShowingDiff: false,
isDiffReady: true,
})
expect(deriveDiffFlags('showing')).toEqual({
status: 'showing',
hasActiveDiff: true,
isShowingDiff: true,
isDiffReady: true,
})
})
it('keeps hasActiveDiff and isDiffReady in lockstep (legacy invariant)', () => {
for (const status of ['none', 'staged', 'showing'] as const) {
const flags = deriveDiffFlags(status)
expect(flags.hasActiveDiff).toBe(flags.isDiffReady)
}
})
})
describe('initial / reset state', () => {
it('starts in the none-derived state', () => {
const state = useWorkflowDiffStore.getState()
expect(state.status).toBe('none')
expect(state.hasActiveDiff).toBe(false)
expect(state.isShowingDiff).toBe(false)
expect(state.isDiffReady).toBe(false)
})
it('RESET_DIFF_STATE carries the none-derived flags and clears diff payload', () => {
expect(RESET_DIFF_STATE.status).toBe('none')
expect(RESET_DIFF_STATE.hasActiveDiff).toBe(false)
expect(RESET_DIFF_STATE.isShowingDiff).toBe(false)
expect(RESET_DIFF_STATE.isDiffReady).toBe(false)
expect(RESET_DIFF_STATE.baselineWorkflow).toBeNull()
expect(RESET_DIFF_STATE.diffAnalysis).toBeNull()
})
})
describe('toggleDiffView', () => {
it('is a guarded no-op when there is no active diff', () => {
seedStatus('none')
useWorkflowDiffStore.getState().toggleDiffView()
expect(useWorkflowDiffStore.getState().status).toBe('none')
})
it('toggles showing → staged (hides the proposed changes)', () => {
seedStatus('showing')
useWorkflowDiffStore.getState().toggleDiffView()
const state = useWorkflowDiffStore.getState()
expect(state.status).toBe('staged')
expect(state.hasActiveDiff).toBe(true)
expect(state.isDiffReady).toBe(true)
expect(state.isShowingDiff).toBe(false)
})
it('toggles staged → showing (reveals the proposed changes)', () => {
seedStatus('staged')
useWorkflowDiffStore.getState().toggleDiffView()
const state = useWorkflowDiffStore.getState()
expect(state.status).toBe('showing')
expect(state.isShowingDiff).toBe(true)
})
})
describe('clearDiff', () => {
it('returns the store to the none status', () => {
seedStatus('showing')
useWorkflowDiffStore.getState().clearDiff({ restoreBaseline: false })
const state = useWorkflowDiffStore.getState()
expect(state.status).toBe('none')
expect(state.hasActiveDiff).toBe(false)
expect(state.isShowingDiff).toBe(false)
expect(state.isDiffReady).toBe(false)
})
})
describe('_batchedStateUpdate (undo/redo writer)', () => {
it('restores the showing status via deriveDiffFlags', () => {
seedStatus('none')
useWorkflowDiffStore.getState()._batchedStateUpdate({
...deriveDiffFlags('showing'),
baselineWorkflow: null,
baselineWorkflowId: 'wf-1',
})
const state = useWorkflowDiffStore.getState()
expect(state.status).toBe('showing')
expect(state.hasActiveDiff).toBe(true)
expect(state.isShowingDiff).toBe(true)
expect(state.isDiffReady).toBe(true)
})
it('the derived booleans always agree with the stored status', () => {
for (const status of ['none', 'staged', 'showing', 'none'] as const) {
seedStatus(status)
const state = useWorkflowDiffStore.getState()
expect({
hasActiveDiff: state.hasActiveDiff,
isShowingDiff: state.isShowingDiff,
isDiffReady: state.isDiffReady,
}).toEqual({
hasActiveDiff: deriveDiffFlags(status).hasActiveDiff,
isShowingDiff: deriveDiffFlags(status).isShowingDiff,
isDiffReady: deriveDiffFlags(status).isDiffReady,
})
}
})
})
})