246 lines
6.9 KiB
TypeScript
246 lines
6.9 KiB
TypeScript
![]() |
import {
|
||
|
modelingMachine,
|
||
|
modelingMachineDefaultContext,
|
||
|
} from '@src/machines/modelingMachine'
|
||
|
import { createActor } from 'xstate'
|
||
|
import { vi } from 'vitest'
|
||
|
import { assertParse, type CallExpressionKw } from '@src/lang/wasm'
|
||
|
import { initPromise } from '@src/lang/wasmUtils'
|
||
|
import {
|
||
|
codeManager,
|
||
|
engineCommandManager,
|
||
|
kclManager,
|
||
|
} from '@src/lib/singletons'
|
||
|
import { VITE_KC_DEV_TOKEN } from '@src/env'
|
||
|
import { line } from '@src/lang/std/sketch'
|
||
|
import { getNodeFromPath } from '@src/lang/queryAst'
|
||
|
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
||
|
import { err } from '@src/lib/trap'
|
||
|
import {
|
||
|
createIdentifier,
|
||
|
createLiteral,
|
||
|
createVariableDeclaration,
|
||
|
} from '@src/lang/create'
|
||
|
|
||
|
// Store original method to restore in afterAll
|
||
|
|
||
|
beforeAll(async () => {
|
||
|
await initPromise
|
||
|
|
||
|
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
|
||
|
await new Promise((resolve) => {
|
||
|
engineCommandManager.start({
|
||
|
token: VITE_KC_DEV_TOKEN,
|
||
|
width: 256,
|
||
|
height: 256,
|
||
|
setMediaStream: () => {},
|
||
|
setIsStreamReady: () => {},
|
||
|
callbackOnEngineLiteConnect: () => {
|
||
|
resolve(true)
|
||
|
},
|
||
|
})
|
||
|
})
|
||
|
}, 30_000)
|
||
|
|
||
|
afterAll(() => {
|
||
|
// Restore the original method
|
||
|
|
||
|
engineCommandManager.tearDown()
|
||
|
})
|
||
|
|
||
|
// Define mock implementations that will be referenced in vi.mock calls
|
||
|
vi.mock('@src/components/SetHorVertDistanceModal', () => ({
|
||
|
createInfoModal: vi.fn(() => ({
|
||
|
open: vi.fn().mockResolvedValue({
|
||
|
value: '10',
|
||
|
segName: 'test',
|
||
|
valueNode: {},
|
||
|
newVariableInsertIndex: 0,
|
||
|
sign: 1,
|
||
|
}),
|
||
|
})),
|
||
|
GetInfoModal: vi.fn(),
|
||
|
}))
|
||
|
|
||
|
vi.mock('@src/components/SetAngleLengthModal', () => ({
|
||
|
createSetAngleLengthModal: vi.fn(() => ({
|
||
|
open: vi.fn().mockResolvedValue({
|
||
|
value: '45',
|
||
|
segName: 'test',
|
||
|
valueNode: {},
|
||
|
newVariableInsertIndex: 0,
|
||
|
sign: 1,
|
||
|
}),
|
||
|
})),
|
||
|
SetAngleLengthModal: vi.fn(),
|
||
|
}))
|
||
|
|
||
|
// Add this function before the test cases
|
||
|
// Utility function to wait for a condition to be met
|
||
|
const waitForCondition = async (
|
||
|
condition: () => boolean,
|
||
|
timeout = 5000,
|
||
|
interval = 100
|
||
|
) => {
|
||
|
const startTime = Date.now()
|
||
|
|
||
|
while (Date.now() - startTime < timeout) {
|
||
|
try {
|
||
|
if (condition()) {
|
||
|
return true
|
||
|
}
|
||
|
} catch {
|
||
|
// Ignore errors, keep polling
|
||
|
}
|
||
|
|
||
|
// Wait for the next interval
|
||
|
await new Promise((resolve) => setTimeout(resolve, interval))
|
||
|
}
|
||
|
|
||
|
// Last attempt before failing
|
||
|
return condition()
|
||
|
}
|
||
|
|
||
|
describe('modelingMachine - XState', () => {
|
||
|
describe('when initialized', () => {
|
||
|
it('should start in the idle state', () => {
|
||
|
const actor = createActor(modelingMachine, {
|
||
|
input: modelingMachineDefaultContext,
|
||
|
}).start()
|
||
|
const state = actor.getSnapshot().value
|
||
|
|
||
|
// The machine should start in the idle state
|
||
|
expect(state).toEqual({ idle: 'hidePlanes' })
|
||
|
})
|
||
|
})
|
||
|
|
||
|
describe('when in sketch mode', () => {
|
||
|
it('should transition to sketch state when entering sketch mode', async () => {
|
||
|
const code = `sketch001 = startSketchOn(XZ)
|
||
|
profile001 = startProfile(sketch001, at = [2263.04, -2721.2])
|
||
|
|> line(end = [16.27, 73.81])
|
||
|
|> line(end = [75.72, 18.41])
|
||
|
`
|
||
|
|
||
|
const ast = assertParse(code)
|
||
|
|
||
|
await kclManager.executeAst({ ast })
|
||
|
|
||
|
expect(kclManager.errors).toEqual([])
|
||
|
|
||
|
const indexOfInterest = code.indexOf('[16.27, 73.81]')
|
||
|
|
||
|
// segment artifact with that source range
|
||
|
const artifact = [...kclManager.artifactGraph].find(
|
||
|
([_, artifact]) =>
|
||
|
artifact?.type === 'segment' &&
|
||
|
artifact.codeRef.range[0] <= indexOfInterest &&
|
||
|
indexOfInterest <= artifact.codeRef.range[1]
|
||
|
)?.[1]
|
||
|
if (!artifact || !('codeRef' in artifact)) {
|
||
|
throw new Error('Artifact not found or invalid artifact structure')
|
||
|
}
|
||
|
|
||
|
const actor = createActor(modelingMachine, {
|
||
|
input: modelingMachineDefaultContext,
|
||
|
}).start()
|
||
|
|
||
|
// Send event to transition to sketch mode
|
||
|
actor.send({
|
||
|
type: 'Set selection',
|
||
|
data: {
|
||
|
selectionType: 'mirrorCodeMirrorSelections',
|
||
|
selection: {
|
||
|
graphSelections: [
|
||
|
{
|
||
|
artifact: artifact,
|
||
|
codeRef: artifact.codeRef,
|
||
|
},
|
||
|
],
|
||
|
otherSelections: [],
|
||
|
},
|
||
|
},
|
||
|
})
|
||
|
actor.send({ type: 'Enter sketch' })
|
||
|
|
||
|
// Check that we're in the sketch state
|
||
|
let state = actor.getSnapshot()
|
||
|
expect(state.value).toBe('animating to existing sketch')
|
||
|
|
||
|
// wait for it to transition
|
||
|
await waitForCondition(() => {
|
||
|
const snapshot = actor.getSnapshot()
|
||
|
return snapshot.value !== 'animating to existing sketch'
|
||
|
}, 5000)
|
||
|
|
||
|
// After the condition is met, do the actual assertion
|
||
|
expect(actor.getSnapshot().value).toEqual({
|
||
|
Sketch: { SketchIdle: 'scene drawn' },
|
||
|
})
|
||
|
|
||
|
const getConstraintInfo = line.getConstraintInfo
|
||
|
const callExp = getNodeFromPath<Node<CallExpressionKw>>(
|
||
|
kclManager.ast,
|
||
|
artifact.codeRef.pathToNode,
|
||
|
'CallExpressionKw'
|
||
|
)
|
||
|
if (err(callExp)) {
|
||
|
throw new Error('Failed to get CallExpressionKw node')
|
||
|
}
|
||
|
const constraintInfo = getConstraintInfo(
|
||
|
callExp.node,
|
||
|
codeManager.code,
|
||
|
artifact.codeRef.pathToNode
|
||
|
)
|
||
|
const first = constraintInfo[0]
|
||
|
|
||
|
// Now that we're in sketchIdle state, test the "Constrain with named value" event
|
||
|
actor.send({
|
||
|
type: 'Constrain with named value',
|
||
|
data: {
|
||
|
currentValue: {
|
||
|
valueText: first.value,
|
||
|
pathToNode: first.pathToNode,
|
||
|
variableName: 'test_variable',
|
||
|
},
|
||
|
// Use type assertion to mock the complex type
|
||
|
namedValue: {
|
||
|
valueText: '20',
|
||
|
variableName: 'test_variable',
|
||
|
insertIndex: 0,
|
||
|
valueCalculated: '20',
|
||
|
variableDeclarationAst: createVariableDeclaration(
|
||
|
'test_variable',
|
||
|
createLiteral('20')
|
||
|
),
|
||
|
variableIdentifierAst: createIdentifier('test_variable') as any,
|
||
|
valueAst: createLiteral('20'),
|
||
|
},
|
||
|
},
|
||
|
})
|
||
|
|
||
|
// Wait for the state to change in response to the constraint
|
||
|
await waitForCondition(() => {
|
||
|
const snapshot = actor.getSnapshot()
|
||
|
// Check if we've transitioned to a different state
|
||
|
return (
|
||
|
JSON.stringify(snapshot.value) !==
|
||
|
JSON.stringify({
|
||
|
Sketch: { SketchIdle: 'set up segments' },
|
||
|
})
|
||
|
)
|
||
|
}, 5000)
|
||
|
|
||
|
await waitForCondition(() => {
|
||
|
const snapshot = actor.getSnapshot()
|
||
|
// Check if we've transitioned to a different state
|
||
|
return (
|
||
|
JSON.stringify(snapshot.value) !==
|
||
|
JSON.stringify({ Sketch: 'Converting to named value' })
|
||
|
)
|
||
|
}, 5000)
|
||
|
expect(codeManager.code).toContain('line(end = [test_variable,')
|
||
|
}, 10_000)
|
||
|
})
|
||
|
})
|