From b123dacc41449340c23e6ec624c3ffdfb12d5910 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Thu, 29 May 2025 22:31:57 +1000 Subject: [PATCH] Replace overlay e2e test with integration tests (#7218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expand xstate unit tests * remove .only 🙄 * add tests for remove constraints (with todos) * expand to invididual constraints too * re-organise * fix tests * fmt * remove log * clean up * type clean up * add delete tests too * remove redundant tests< * fix e2e * lints --- .../testing-segment-overlays.spec.ts | 918 +----------- src/lang/modifyAst.test.ts | 189 --- src/lang/std/sketch.test.ts | 44 +- src/lang/std/sketch.ts | 224 +-- src/lang/std/sketchcombos.ts | 9 +- src/machines/modelingMachine.test.ts | 1292 +++++++++++++++-- 6 files changed, 1363 insertions(+), 1313 deletions(-) diff --git a/e2e/playwright/testing-segment-overlays.spec.ts b/e2e/playwright/testing-segment-overlays.spec.ts index 33f335bc2..08ffbb721 100644 --- a/e2e/playwright/testing-segment-overlays.spec.ts +++ b/e2e/playwright/testing-segment-overlays.spec.ts @@ -50,6 +50,11 @@ test.describe('Testing segment overlays', () => { y = 0 x = hoverPos.x + Math.cos(ang * deg) * 32 y = hoverPos.y - Math.sin(ang * deg) * 32 + + const constrainedLocator = page.locator( + `[data-constraint-type="${constraintType}"][data-is-constrained="true"]` + ) + await page.mouse.move(x, y) await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator) await page.mouse.move(x, y) @@ -57,9 +62,6 @@ test.describe('Testing segment overlays', () => { await editor.expectEditor.toContain(expectBeforeUnconstrained, { shouldNormalise: true, }) - const constrainedLocator = page.locator( - `[data-constraint-type="${constraintType}"][data-is-constrained="true"]` - ) await expect(constrainedLocator).toBeVisible() await constrainedLocator.hover() await expect( @@ -90,15 +92,12 @@ test.describe('Testing segment overlays', () => { await expect( page.getByTestId('cmd-bar-arg-value').getByRole('textbox') ).toBeFocused() + await page.waitForTimeout(500) await page .getByRole('button', { name: 'arrow right Continue', }) .click() - await expect(page.locator('.cm-content')).toContainText(expectFinal) - await editor.expectEditor.toContain(expectFinal, { - shouldNormalise: true, - }) await editor.expectEditor.toContain(expectFinal, { shouldNormalise: true, }) @@ -163,6 +162,7 @@ test.describe('Testing segment overlays', () => { await expect( page.getByTestId('cmd-bar-arg-value').getByRole('textbox') ).toBeFocused() + await page.waitForTimeout(500) await page .getByRole('button', { name: 'arrow right Continue', @@ -195,10 +195,12 @@ test.describe('Testing segment overlays', () => { }) } test.setTimeout(120000) - test('for segments [line, angledLine, xLineTo]', async ({ + test('for a line segment', async ({ page, editor, homePage, + scene, + cmdBar, }) => { await page.addInitScript(async () => { localStorage.setItem( @@ -206,7 +208,7 @@ test.describe('Testing segment overlays', () => { `@settings(defaultLengthUnit = in) part001 = startSketchOn(XZ) |> startProfile(at = [5 + 0, 20 + 0]) - |> line(end = [0.5, -14 + 0]) + |> line(end = [0.5, -12 + 0]) |> angledLine(angle = 3 + 0, length = 32 + 0) |> line(endAbsolute = [5 + 33, 20 + 11.5 + 0]) |> xLine(endAbsolute = 5 + 9 - 5) @@ -226,18 +228,16 @@ test.describe('Testing segment overlays', () => { await page.setBodyDimensions({ width: 1200, height: 500 }) await homePage.goToModelingScene() + await await scene.settled(cmdBar) // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() await page.getByText('xLine(endAbsolute = 5 + 9 - 5)').click() await page.waitForTimeout(100) await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.waitForTimeout(500) - await expect(page.getByTestId('segment-overlay')).toHaveCount(13) + await expect(page.getByTestId('segment-overlay')).toHaveCount(14) const clickUnconstrained = _clickUnconstrained(page, editor) const clickConstrained = _clickConstrained(page, editor) @@ -261,22 +261,22 @@ test.describe('Testing segment overlays', () => { type: 'default_camera_get_settings', }, }) - await page.waitForTimeout(100) + await page.waitForTimeout(1000) await u.closeDebugPanel() let ang = 0 - const line = await u.getBoundingBox('[data-overlay-index="0"]') - ang = await u.getAngle('[data-overlay-index="0"]') + const line = await u.getBoundingBox('[data-overlay-index="1"]') + ang = await u.getAngle('[data-overlay-index="1"]') console.log('line1', line, ang) await clickConstrained({ hoverPos: { x: line.x, y: line.y }, constraintType: 'yRelative', - expectBeforeUnconstrained: '|> line(end = [0.5, -14 + 0])', - expectAfterUnconstrained: '|> line(end = [0.5, -14])', + expectBeforeUnconstrained: '|> line(end = [0.5, -12 + 0])', + expectAfterUnconstrained: '|> line(end = [0.5, -12])', expectFinal: '|> line(end = [0.5, yRel001])', ang: ang + 180, - locator: '[data-overlay-toolbar-index="0"]', + locator: '[data-overlay-toolbar-index="1"]', }) console.log('line2') await clickUnconstrained({ @@ -286,651 +286,7 @@ test.describe('Testing segment overlays', () => { expectAfterUnconstrained: 'line(end = [xRel001, yRel001])', expectFinal: '|> line(end = [0.5, yRel001])', ang: ang + 180, - locator: '[data-overlay-index="0"]', - }) - - const angledLine = await u.getBoundingBox('[data-overlay-index="1"]') - ang = await u.getAngle('[data-overlay-index="1"]') - console.log('angledLine1') - await clickConstrained({ - hoverPos: { x: angledLine.x, y: angledLine.y }, - constraintType: 'angle', - expectBeforeUnconstrained: 'angledLine(angle = 3 + 0, length = 32 + 0)', - expectAfterUnconstrained: 'angledLine(angle = 3, length = 32 + 0)', - expectFinal: 'angledLine(angle = angle001, length = 32 + 0)', - ang: ang + 180, - locator: '[data-overlay-toolbar-index="1"]', - }) - console.log('angledLine2') - await clickConstrained({ - hoverPos: { x: angledLine.x, y: angledLine.y }, - constraintType: 'length', - expectBeforeUnconstrained: - 'angledLine(angle = angle001, length = 32 + 0)', - expectAfterUnconstrained: 'angledLine(angle = angle001, length = 32)', - expectFinal: 'angledLine(angle = angle001, length = len001)', - ang: ang + 180, - locator: '[data-overlay-toolbar-index="1"]', - }) - - await page.mouse.move(700, 250) - await page.waitForTimeout(100) - - let lineTo = await u.getBoundingBox('[data-overlay-index="2"]') - ang = await u.getAngle('[data-overlay-index="2"]') - console.log('lineTo1') - await clickConstrained({ - hoverPos: { x: lineTo.x, y: lineTo.y }, - constraintType: 'yAbsolute', - expectBeforeUnconstrained: - 'line(endAbsolute = [5 + 33, 20 + 11.5 + 0])', - expectAfterUnconstrained: 'line(endAbsolute = [5 + 33, 31.5])', - expectFinal: 'line(endAbsolute = [5 + 33, yAbs001])', - steps: 8, - ang: ang + 180, - locator: '[data-overlay-toolbar-index="2"]', - }) - console.log('lineTo2') - await clickConstrained({ - hoverPos: { x: lineTo.x, y: lineTo.y }, - constraintType: 'xAbsolute', - expectBeforeUnconstrained: 'line(endAbsolute = [5 + 33, yAbs001])', - expectAfterUnconstrained: 'line(endAbsolute = [38, yAbs001])', - expectFinal: 'line(endAbsolute = [xAbs001, yAbs001])', - steps: 8, - ang: ang + 180, - locator: '[data-overlay-toolbar-index="2"]', - }) - - const xLineTo = await u.getBoundingBox('[data-overlay-index="3"]') - ang = await u.getAngle('[data-overlay-index="3"]') - console.log('xlineTo1') - await clickConstrained({ - hoverPos: { x: xLineTo.x, y: xLineTo.y }, - constraintType: 'xAbsolute', - expectBeforeUnconstrained: 'xLine(endAbsolute = 5 + 9 - 5)', - expectAfterUnconstrained: 'xLine(endAbsolute = 9)', - expectFinal: 'xLine(endAbsolute = xAbs002)', - ang: ang + 180, - steps: 8, - locator: '[data-overlay-toolbar-index="3"]', - }) - }) - - // Broken on main at time of writing! - test('for segments [yLineTo, xLine]', async ({ - page, - editor, - homePage, - }) => { - await page.addInitScript(async () => { - localStorage.setItem( - 'persistCode', - `@settings(defaultLengthUnit = in) - yRel001 = -14 - xRel001 = 0.5 - angle001 = 3 - len001 = 32 - yAbs001 = 11.5 - xAbs001 = 33 - xAbs002 = 4 - part001 = startSketchOn(XZ) - |> startProfile(at = [0, 0]) - |> line(end = [0.5, yRel001]) - |> angledLine(angle = angle001, length = len001) - |> line(endAbsolute = [33, yAbs001]) - |> xLine(endAbsolute = xAbs002) - |> yLine(endAbsolute = -10.77, tag = $a) - |> xLine(length = 26.04) - |> yLine(length = 21.14 + 0) - |> angledLine(angle = 181 + 0, lengthX = 23.14) - ` - ) - }) - const u = await getUtils(page) - await page.setBodyDimensions({ width: 1200, height: 500 }) - - await homePage.goToModelingScene() - - // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() - - await page.getByText('xLine(length = 26.04)').click() - await page.waitForTimeout(100) - await page.getByRole('button', { name: 'Edit Sketch' }).click() - await page.waitForTimeout(500) - - await expect(page.getByTestId('segment-overlay')).toHaveCount(8) - - const clickUnconstrained = _clickUnconstrained(page, editor) - - await page.mouse.move(700, 250) - await page.waitForTimeout(100) - - let ang = 0 - - const yLineTo = await u.getBoundingBox('[data-overlay-index="4"]') - ang = await u.getAngle('[data-overlay-index="4"]') - console.log('ylineTo1') - await clickUnconstrained({ - hoverPos: { x: yLineTo.x, y: yLineTo.y - 200 }, - constraintType: 'yAbsolute', - expectBeforeUnconstrained: 'yLine(endAbsolute = -10.77, tag = $a)', - expectAfterUnconstrained: 'yLine(endAbsolute = yAbs002, tag = $a)', - expectFinal: 'yLine(endAbsolute = -10.77, tag = $a)', - ang: ang + 180, - locator: '[data-overlay-toolbar-index="4"]', - }) - - const xLine = await u.getBoundingBox('[data-overlay-index="5"]') - ang = await u.getAngle('[data-overlay-index="5"]') - console.log('xline') - await clickUnconstrained({ - hoverPos: { x: xLine.x, y: xLine.y }, - constraintType: 'xRelative', - expectBeforeUnconstrained: 'xLine(length = 26.04)', - expectAfterUnconstrained: 'xLine(length = xRel002)', - expectFinal: 'xLine(length = 26.04)', - steps: 10, - ang: ang + 180, - locator: '[data-overlay-toolbar-index="5"]', - }) - }) - test('for segments [yLine, angledLineOfXLength, angledLineOfYLength]', async ({ - page, - editor, - homePage, - }) => { - await page.addInitScript(async () => { - localStorage.setItem( - 'persistCode', - `@settings(defaultLengthUnit = in) - part001 = startSketchOn(XZ) - |> startProfile(at = [0, 0]) - |> line(end = [0.5, -14 + 0]) - |> angledLine(angle = 3 + 0, length = 32 + 0) - |> line(endAbsolute = [33, 11.5 + 0]) - |> xLine(endAbsolute = 9 - 5) - |> yLine(endAbsolute = -10.77, tag = $a) - |> xLine(length = 26.04) - |> yLine(length = 21.14 + 0) - |> angledLine(angle = 181 + 0, lengthX = 23.14) - |> angledLine(angle = -91, lengthY = 19 + 0) - |> angledLine(angle = 3 + 0, endAbsoluteX = 26) - |> angledLine(angle = 89, endAbsoluteY = 9.14 + 0) - |> angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9) - |> tangentialArc(endAbsolute = [3.14 + 13, 3.14]) - ` - ) - localStorage.setItem('disableAxis', 'true') - }) - const u = await getUtils(page) - await page.setBodyDimensions({ width: 1200, height: 500 }) - - await homePage.goToModelingScene() - - // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() - await page.waitForTimeout(500) - - await page.getByText('xLine(endAbsolute = 9 - 5)').click() - await page.waitForTimeout(100) - await page.getByRole('button', { name: 'Edit Sketch' }).click() - await page.waitForTimeout(500) - - await expect(page.getByTestId('segment-overlay')).toHaveCount(13) - - const clickUnconstrained = _clickUnconstrained(page, editor) - const clickConstrained = _clickConstrained(page, editor) - - let ang = 0 - - const yLine = await u.getBoundingBox('[data-overlay-index="6"]') - ang = await u.getAngle('[data-overlay-index="6"]') - console.log('yline1') - await clickConstrained({ - hoverPos: { x: yLine.x, y: yLine.y }, - constraintType: 'yRelative', - expectBeforeUnconstrained: 'yLine(length = 21.14 + 0)', - expectAfterUnconstrained: 'yLine(length = 21.14)', - expectFinal: 'yLine(length = yRel001)', - ang: ang + 180, - locator: '[data-overlay-toolbar-index="6"]', - }) - - const angledLineOfXLength = await u.getBoundingBox( - '[data-overlay-index="7"]' - ) - ang = await u.getAngle('[data-overlay-index="7"]') - console.log('angledLineOfXLength1') - await clickConstrained({ - hoverPos: { x: angledLineOfXLength.x, y: angledLineOfXLength.y }, - constraintType: 'angle', - expectBeforeUnconstrained: - 'angledLine(angle = 181 + 0, lengthX = 23.14)', - expectAfterUnconstrained: 'angledLine(angle = -179, lengthX = 23.14)', - expectFinal: 'angledLine(angle = angle001, lengthX = 23.14)', - ang: ang + 180, - locator: '[data-overlay-toolbar-index="7"]', - }) - console.log('angledLineOfXLength2') - await clickUnconstrained({ - hoverPos: { x: angledLineOfXLength.x, y: angledLineOfXLength.y }, - constraintType: 'xRelative', - expectBeforeUnconstrained: - 'angledLine(angle = angle001, lengthX = 23.14)', - expectAfterUnconstrained: - 'angledLine(angle = angle001, lengthX = xRel001)', - expectFinal: 'angledLine(angle = angle001, lengthX = 23.14)', - steps: 7, - ang: ang + 180, - locator: '[data-overlay-toolbar-index="7"]', - }) - - const angledLineOfYLength = await u.getBoundingBox( - '[data-overlay-index="8"]' - ) - ang = await u.getAngle('[data-overlay-index="8"]') - console.log('angledLineOfYLength1') - await clickUnconstrained({ - hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y }, - constraintType: 'angle', - expectBeforeUnconstrained: 'angledLine(angle = -91, lengthY = 19 + 0)', - expectAfterUnconstrained: - 'angledLine(angle = angle002, lengthY = 19 + 0)', - expectFinal: 'angledLine(angle = -91, lengthY = 19 + 0)', - ang: ang + 180, - steps: 6, - locator: '[data-overlay-toolbar-index="8"]', - }) - console.log('angledLineOfYLength2') - await clickConstrained({ - hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y }, - constraintType: 'yRelative', - expectBeforeUnconstrained: 'angledLine(angle = -91, lengthY = 19 + 0)', - expectAfterUnconstrained: 'angledLine(angle = -91, lengthY = 19)', - expectFinal: 'angledLine(angle = -91, lengthY = yRel002)', - ang: ang + 180, - steps: 7, - locator: '[data-overlay-toolbar-index="8"]', - }) - }) - test('for segments [angledLineToX, angledLineToY, angledLineThatIntersects]', async ({ - page, - editor, - homePage, - }) => { - await page.addInitScript(async () => { - localStorage.setItem( - 'persistCode', - `@settings(defaultLengthUnit = in) - part001 = startSketchOn(XZ) - |> startProfile(at = [0, 0]) - |> line(end = [0.5, -14 + 0]) - |> angledLine(angle = 3 + 0, length = 32 + 0) - |> line(endAbsolute = [33, 11.5 + 0]) - |> xLine(endAbsolute = 9 - 5) - |> yLine(endAbsolute = -10.77, tag = $a) - |> xLine(length = 26.04) - |> yLine(length = 21.14 + 0) - |> angledLine(angle = 181 + 0, lengthX = 23.14) - |> angledLine(angle = -91, lengthY = 19 + 0) - |> angledLine(angle = 3 + 0, endAbsoluteX = 26) - |> angledLine(angle = 89, endAbsoluteY = 9.14 + 0) - |> angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9) - |> tangentialArc(endAbsolute = [3.14 + 13, 1.14]) - ` - ) - localStorage.setItem('disableAxis', 'true') - }) - const u = await getUtils(page) - await page.setBodyDimensions({ width: 1200, height: 500 }) - - await homePage.goToModelingScene() - - // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() - - await page.getByText('xLine(endAbsolute = 9 - 5)').click() - await page.waitForTimeout(100) - await page.getByRole('button', { name: 'Edit Sketch' }).click() - await page.waitForTimeout(500) - - await expect(page.getByTestId('segment-overlay')).toHaveCount(13) - - const clickUnconstrained = _clickUnconstrained(page, editor) - const clickConstrained = _clickConstrained(page, editor) - - let ang = 0 - - const angledLineToX = await u.getBoundingBox('[data-overlay-index="9"]') - ang = await u.getAngle('[data-overlay-index="9"]') - console.log('angledLineToX') - await clickConstrained({ - hoverPos: { x: angledLineToX.x, y: angledLineToX.y }, - constraintType: 'angle', - expectBeforeUnconstrained: - 'angledLine(angle = 3 + 0, endAbsoluteX = 26)', - expectAfterUnconstrained: 'angledLine(angle = 3, endAbsoluteX = 26)', - expectFinal: 'angledLine(angle = angle001, endAbsoluteX = 26)', - ang: ang + 180, - locator: '[data-overlay-toolbar-index="9"]', - }) - console.log('angledLineToX2') - await clickUnconstrained({ - hoverPos: { x: angledLineToX.x, y: angledLineToX.y }, - constraintType: 'xAbsolute', - expectBeforeUnconstrained: - 'angledLine(angle = angle001, endAbsoluteX = 26)', - expectAfterUnconstrained: - 'angledLine(angle = angle001, endAbsoluteX = xAbs001)', - expectFinal: 'angledLine(angle = angle001, endAbsoluteX = 26)', - ang: ang + 180, - locator: '[data-overlay-toolbar-index="9"]', - }) - - const angledLineToY = await u.getBoundingBox('[data-overlay-index="10"]') - ang = await u.getAngle('[data-overlay-index="10"]') - console.log('angledLineToY') - await clickUnconstrained({ - hoverPos: { x: angledLineToY.x, y: angledLineToY.y }, - constraintType: 'angle', - expectBeforeUnconstrained: 'angledLine(angle = 89, to = 9.14 + 0)', - expectAfterUnconstrained: 'angledLine(angle = angle002, to = 9.14 + 0)', - expectFinal: 'angledLine(angle = 89, to = 9.14 + 0)', - steps: process.platform === 'darwin' ? 8 : 9, - ang: ang + 180, - locator: '[data-overlay-toolbar-index="10"]', - }) - console.log('angledLineToY2') - await clickConstrained({ - hoverPos: { x: angledLineToY.x, y: angledLineToY.y }, - constraintType: 'yAbsolute', - expectBeforeUnconstrained: - 'angledLine(angle = 89, endAbsoluteY = 9.14 + 0)', - expectAfterUnconstrained: 'angledLine(angle = 89, endAbsoluteY = 9.14)', - expectFinal: 'angledLine(angle = 89, endAbsoluteY = yAbs001)', - ang: ang + 180, - locator: '[data-overlay-toolbar-index="10"]', - }) - - const angledLineThatIntersects = await u.getBoundingBox( - '[data-overlay-index="11"]' - ) - ang = await u.getAngle('[data-overlay-index="11"]') - console.log('angledLineThatIntersects') - await clickUnconstrained({ - hoverPos: { - x: angledLineThatIntersects.x, - y: angledLineThatIntersects.y, - }, - constraintType: 'angle', - expectBeforeUnconstrained: `angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)`, - expectAfterUnconstrained: `angledLineThatIntersects(angle = angle003, intersectTag = a,offset = 9)`, - expectFinal: `angledLineThatIntersects(angle = -176, offset = 9, intersectTag = a)`, - ang: ang + 180, - locator: '[data-overlay-toolbar-index="11"]', - }) - console.log('angledLineThatIntersects2') - await clickUnconstrained({ - hoverPos: { - x: angledLineThatIntersects.x, - y: angledLineThatIntersects.y, - }, - constraintType: 'intersectionOffset', - expectBeforeUnconstrained: `angledLineThatIntersects(angle = -176, offset = 9, intersectTag = a)`, - expectAfterUnconstrained: `angledLineThatIntersects(angle = -176, offset = perpDist001, intersectTag = a)`, - expectFinal: `angledLineThatIntersects(angle = -176, offset = 9, intersectTag = a)`, - ang: ang + 180, - locator: '[data-overlay-toolbar-index="11"]', - }) - }) - test('for segment [tangentialArc]', async ({ - page, - editor, - homePage, - scene, - cmdBar, - }) => { - await page.addInitScript(async () => { - localStorage.setItem( - 'persistCode', - `@settings(defaultLengthUnit = in) - part001 = startSketchOn(XZ) - |> startProfile(at = [0, 0]) - |> line(end = [0.5, -14 + 0]) - |> angledLine(angle = 3 + 0, length = 32 + 0) - |> line(endAbsolute = [33, 11.5 + 0]) - |> xLine(endAbsolute = 9 - 5) - |> yLine(endAbsolute = -10.77, tag = $a) - |> xLine(length = 26.04) - |> yLine(length = 21.14 + 0) - |> angledLine(angle = 181 + 0, lengthX = 23.14) - |> angledLine(angle = -91, lengthY = 19 + 0) - |> angledLine(angle = 3 + 0, endAbsoluteX = 26) - |> angledLine(angle = 89, endAbsoluteY = 9.14 + 0) - |> angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9) - |> tangentialArc(endAbsolute = [3.14 + 13, -3.14]) - ` - ) - localStorage.setItem('disableAxis', 'true') - }) - const u = await getUtils(page) - await page.setBodyDimensions({ width: 1200, height: 500 }) - - await homePage.goToModelingScene() - await scene.settled(cmdBar) - - // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() - - await page.getByText('xLine(endAbsolute = 9 - 5)').click() - await page.waitForTimeout(100) - await page.getByRole('button', { name: 'Edit Sketch' }).click() - await page.waitForTimeout(500) - - await expect(page.getByTestId('segment-overlay')).toHaveCount(14) - - const clickUnconstrained = _clickUnconstrained(page, editor) - const clickConstrained = _clickConstrained(page, editor) - - const tangentialArc = await u.getBoundingBox('[data-overlay-index="13"]') - let ang = await u.getAngle('[data-overlay-index="13"]') - console.log('tangentialArc') - await clickConstrained({ - hoverPos: { x: tangentialArc.x, y: tangentialArc.y }, - constraintType: 'xAbsolute', - expectBeforeUnconstrained: - 'tangentialArc(endAbsolute = [3.14 + 13, -3.14])', - expectAfterUnconstrained: 'tangentialArc(endAbsolute = [16.14, -3.14])', - expectFinal: 'tangentialArc(endAbsolute = [xAbs001, -3.14])', - ang: ang + 180, - steps: 6, - locator: '[data-overlay-toolbar-index="13"]', - }) - console.log('tangentialArc2') - await clickUnconstrained({ - hoverPos: { x: tangentialArc.x, y: tangentialArc.y }, - constraintType: 'yAbsolute', - expectBeforeUnconstrained: - 'tangentialArc(endAbsolute = [xAbs001, -3.14])', - expectAfterUnconstrained: - 'tangentialArc(endAbsolute = [xAbs001, yAbs001])', - expectFinal: 'tangentialArc(endAbsolute = [xAbs001, -3.14])', - ang: ang + 180, - steps: 10, - locator: '[data-overlay-toolbar-index="13"]', - }) - }) - test('for segment [arcTo]', async ({ - page, - editor, - homePage, - scene, - cmdBar, - }) => { - await page.addInitScript(async () => { - localStorage.setItem( - 'persistCode', - `@settings(defaultLengthUnit = in) -sketch001 = startSketchOn(XZ) -profile001 = startProfile(sketch001, at = [56.37, 120.33]) - |> line(end = [162.86, 106.48]) - |> arc( - interiorAbsolute = [360.16, 231.76], - endAbsolute = [391.48, 131.54], - ) - |> yLine(length = -131.54) - |> arc(angleStart = 33.53, angleEnd = -141.07, radius = 126.46) -` - ) - localStorage.setItem('disableAxis', 'true') - }) - const u = await getUtils(page) - await page.setBodyDimensions({ width: 1200, height: 500 }) - - await homePage.goToModelingScene() - await scene.connectionEstablished() - await scene.settled(cmdBar) - - // wait for execution done - - await page.getByText('line(end = [162.86, 106.48])').click() - await page.waitForTimeout(100) - await page.getByRole('button', { name: 'Edit Sketch' }).click() - await page.waitForTimeout(500) - - await expect(page.getByTestId('segment-overlay')).toHaveCount(5) - - const clickUnconstrained = _clickUnconstrained(page, editor) - const clickConstrained = _clickConstrained(page, editor) - - const arcTo = await u.getBoundingBox('[data-overlay-index="1"]') - let ang = await u.getAngle('[data-overlay-index="1"]') - console.log('arcTo interiorAbsolute x') - await clickUnconstrained({ - hoverPos: { x: arcTo.x, y: arcTo.y }, - constraintType: 'xAbsolute', - expectBeforeUnconstrained: `arc(interiorAbsolute = [360.16, 231.76], endAbsolute = [391.48, 131.54])`, - expectAfterUnconstrained: `arc(interiorAbsolute = [360.16, 231.76], endAbsolute = [391.48, 131.54])`, - expectFinal: `arc(interiorAbsolute = [xAbs001, 231.76], endAbsolute = [391.48, 131.54])`, - ang: ang, - steps: 6, - locator: '[data-overlay-toolbar-index="1"]', - }) - - console.log('arcTo interiorAbsolute y') - await clickUnconstrained({ - hoverPos: { x: arcTo.x, y: arcTo.y }, - constraintType: 'yAbsolute', - expectBeforeUnconstrained: `arc(interiorAbsolute = [xAbs001, 231.76], endAbsolute = [391.48, 131.54])`, - expectAfterUnconstrained: `arc(interiorAbsolute = [xAbs001, yAbs001], endAbsolute = [391.48, 131.54])`, - expectFinal: `arc(interiorAbsolute = [xAbs001, 231.76], endAbsolute = [391.48, 131.54])`, - ang: ang, - steps: 10, - locator: '[data-overlay-toolbar-index="1"]', - }) - - console.log('arcTo end x') - await clickConstrained({ - hoverPos: { x: arcTo.x, y: arcTo.y }, - constraintType: 'xAbsolute', - expectBeforeUnconstrained: `arc(interiorAbsolute = [xAbs001, 231.76], endAbsolute = [391.48, 131.54])`, - expectAfterUnconstrained: `arc(interiorAbsolute = [xAbs001, 231.76], endAbsolute = [391.48, 131.54])`, - expectFinal: `arc(interiorAbsolute = [xAbs001, 231.76], endAbsolute = [xAbs002, 131.54])`, - ang: ang + 180, - steps: 6, - locator: '[data-overlay-toolbar-index="1"]', - }) - - console.log('arcTo end y') - await clickUnconstrained({ - hoverPos: { x: arcTo.x, y: arcTo.y }, - constraintType: 'yAbsolute', - expectBeforeUnconstrained: `arc(interiorAbsolute = [xAbs001, 231.76], endAbsolute = [xAbs002, 131.54])`, - expectAfterUnconstrained: `arc(interiorAbsolute = [xAbs001, 231.76], endAbsolute = [xAbs002, yAbs002])`, - expectFinal: `arc(interiorAbsolute = [xAbs001, 231.76], endAbsolute = [xAbs002, 131.54])`, - ang: ang + 180, - steps: 10, - locator: '[data-overlay-toolbar-index="1"]', - }) - }) - test('for segment [circle]', async ({ page, editor, homePage }) => { - await page.addInitScript(async () => { - localStorage.setItem( - 'persistCode', - `@settings(defaultLengthUnit = in) -part001 = startSketchOn(XZ) - |> circle(center = [1 + 0, 0], radius = 8) - ` - ) - localStorage.setItem('disableAxis', 'true') - }) - const u = await getUtils(page) - await page.setBodyDimensions({ width: 1200, height: 500 }) - - await homePage.goToModelingScene() - - // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() - - await page.getByText('circle(center = [1 + 0, 0], radius = 8)').click() - await page.waitForTimeout(100) - await page.getByRole('button', { name: 'Edit Sketch' }).click() - await page.waitForTimeout(500) - - await expect(page.getByTestId('segment-overlay')).toHaveCount(1) - - const clickUnconstrained = _clickUnconstrained(page, editor) - const clickConstrained = _clickConstrained(page, editor) - - const hoverPos = { x: 789, y: 114 } as const - let ang = await u.getAngle('[data-overlay-index="0"]') - console.log('angl', ang) - console.log('circle center x') - await clickConstrained({ - hoverPos, - constraintType: 'xAbsolute', - expectBeforeUnconstrained: 'circle(center = [1 + 0, 0], radius = 8)', - expectAfterUnconstrained: 'circle(center = [1, 0], radius = 8)', - expectFinal: 'circle(center = [xAbs001, 0], radius = 8)', - ang: ang + 105, - steps: 6, - locator: '[data-overlay-toolbar-index="0"]', - }) - console.log('circle center y') - await clickUnconstrained({ - hoverPos, - constraintType: 'yAbsolute', - expectBeforeUnconstrained: 'circle(center = [xAbs001, 0], radius = 8)', - expectAfterUnconstrained: - 'circle(center = [xAbs001, yAbs001], radius = 8)', - expectFinal: 'circle(center = [xAbs001, 0], radius = 8)', - ang: ang + 180, - steps: 30, - locator: '[data-overlay-toolbar-index="0"]', - }) - console.log('circle radius') - await clickUnconstrained({ - hoverPos, - constraintType: 'radius', - expectBeforeUnconstrained: 'circle(center = [xAbs001, 0], radius = 8)', - expectAfterUnconstrained: - 'circle(center = [xAbs001, 0], radius = radius001)', - expectFinal: 'circle(center = [xAbs001, 0], radius = 8)', - ang: ang + 105, - steps: 10, - locator: '[data-overlay-toolbar-index="0"]', + locator: '[data-overlay-index="1"]', }) }) }) @@ -978,7 +334,7 @@ part001 = startSketchOn(XZ) shouldNormalise: true, }) } - test('all segment types', async ({ + test('a line segment', async ({ page, editor, homePage, @@ -1045,201 +401,6 @@ part001 = startSketchOn(XZ) steps: 6, locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: `arc(interiorAbsolute = [16.25, 5.12], endAbsolute = [21.61, 4.15])`, - stdLibFnName: 'arc', - ang: ang, - steps: 6, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'tangentialArc(endAbsolute = [3.14 + 13, 1.14])', - stdLibFnName: 'tangentialArc', - ang: ang + 180, - steps: 6, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: `angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)`, - stdLibFnName: 'angledLineThatIntersects', - ang: ang + 180, - steps: 7, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'angledLine(angle = 89, endAbsoluteY = 9.14 + 0)', - stdLibFnName: 'angledLineToY', - ang: ang + 180, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'angledLine(angle = 3 + 0, endAbsoluteX = 26)', - stdLibFnName: 'angledLineToX', - ang: ang + 180, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'angledLine(angle = -91, lengthY = 19 + 0)', - stdLibFnName: 'angledLineOfYLength', - ang: ang + 180, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'angledLine(angle = 181 + 0, lengthX = 23.14)', - stdLibFnName: 'angledLineOfXLength', - ang: ang + 180, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'yLine(length = 21.14 + 0)', - stdLibFnName: 'yLine', - ang: ang + 180, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'xLine(length = 26.04)', - stdLibFnName: 'xLine', - ang: ang + 180, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'yLine(endAbsolute = -10.77, tag = $a)', - stdLibFnName: 'yLineTo', - ang: ang + 180, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'xLine(endAbsolute = 9 - 5)', - stdLibFnName: 'xLineTo', - ang: ang + 180, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await expect(page.getByText('Added variable')).not.toBeVisible() - - const hoverPos = { x: segmentToDelete.x, y: segmentToDelete.y } - await page.mouse.move(0, 0) - await page.waitForTimeout(1000) - await page.mouse.move(hoverPos.x, hoverPos.y) - await wiggleMove( - page, - hoverPos.x, - hoverPos.y, - 20, - 30, - ang, - 10, - 5, - `[data-overlay-toolbar-index="${overlayIndex}"]` - ) - await page.mouse.move(hoverPos.x, hoverPos.y) - - const codeToBeDeleted = 'line(endAbsolute = [33, 11.5 + 0])' - await editor.expectEditor.toContain(codeToBeDeleted, { - shouldNormalise: true, - }) - - await page.getByTestId('overlay-menu').click() - await page.getByText('Delete Segment').click() - - await editor.expectEditor.not.toContain(codeToBeDeleted, { - shouldNormalise: true, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'angledLine(angle = 3 + 0, length = 32 + 0)', - stdLibFnName: 'angledLine', - ang: ang + 180, - locator: `[data-overlay-toolbar-index="${overlayIndex}"]`, - }) - - overlayIndex-- - - segmentToDelete = await getOverlayByIndex(overlayIndex) - ang = await u.getAngle(`[data-overlay-index="${overlayIndex}"]`) - await deleteSegmentSequence({ - hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y }, - codeToBeDeleted: 'line(end = [0.5, -14 + 0])', - stdLibFnName: 'line', - ang: ang + 180, - }) - - await page.waitForTimeout(200) }) }) test.describe('Testing delete with dependent segments', () => { @@ -1381,43 +542,6 @@ part001 = startSketchOn(XZ) before: `line(end = [22 + 0, 2 + 0], tag = $seg01)`, after: `line(end = [22, 2], tag = $seg01)`, }, - - { - before: `angledLine(angle = 5 + 0, length = 23.03 + 0, tag = $seg01)`, - after: `line(end = [22.94, 2.01], tag = $seg01)`, - }, - { - before: `xLine(length = 23 + 0, tag = $seg01)`, - after: `line(end = [23, 0], tag = $seg01)`, - }, - { - before: `yLine(length = -8 + 0, tag = $seg01)`, - after: `line(end = [0, -8], tag = $seg01)`, - }, - { - before: `xLine(endAbsolute = 30 + 0, tag = $seg01)`, - after: `line(end = [25, 0], tag = $seg01)`, - }, - { - before: `yLine(endAbsolute = -4 + 0, tag = $seg01)`, - after: `line(end = [0, -10], tag = $seg01)`, - }, - { - before: `angledLine(angle = 3 + 0, lengthX = 30 + 0, tag = $seg01)`, - after: `line(end = [30, 1.57], tag = $seg01)`, - }, - { - before: `angledLine(angle = 3 + 0, lengthY = 1.5 + 0, tag = $seg01)`, - after: `line(end = [28.62, 1.5], tag = $seg01)`, - }, - { - before: `angledLine(angle = 3 + 0, endAbsoluteX = 30 + 0, tag = $seg01)`, - after: `line(end = [25, 1.31], tag = $seg01)`, - }, - { - before: `angledLine(angle = 3 + 0, endAbsoluteY = 7 + 0, tag = $seg01)`, - after: `line(end = [19.08, 1], tag = $seg01)`, - }, ] for (const { before, after } of cases) { diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts index b0168ca82..f65d4c6ed 100644 --- a/src/lang/modifyAst.test.ts +++ b/src/lang/modifyAst.test.ts @@ -16,7 +16,6 @@ import { addSketchTo, deleteSegmentFromPipeExpression, moveValueIntoNewVariable, - removeSingleConstraintInfo, sketchOnExtrudedFace, splitPipedProfile, } from '@src/lang/modifyAst' @@ -24,11 +23,6 @@ import { findUsesOfTagInPipe } from '@src/lang/queryAst' import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils' import type { Artifact } from '@src/lang/std/artifactGraph' import { codeRefFromRange } from '@src/lang/std/artifactGraph' -import type { - InputArg, - InputArgKeys, - SimplifiedArgDetails, -} from '@src/lang/std/stdTypes' import { topLevelRange } from '@src/lang/util' import type { Identifier, Literal, LiteralValue } from '@src/lang/wasm' import { assertParse, recast } from '@src/lang/wasm' @@ -666,189 +660,6 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine(angle = -65, length = ${ }) }) -describe('Testing removeSingleConstraintInfo', () => { - describe('with mostly object notation', () => { - const code = `part001 = startSketchOn(-XZ) - |> startProfile(at = [0, 0]) - |> line(end = [3 + 0, 4 + 0]) - |> /*0*/ angledLine(angle = 3 + 0, length = 3.14 + 0) - |> line(endAbsolute = [6.14 + 0, 3.14 + 0]) - |> xLine(/*xAbs*/ endAbsolute = 8 + 0) - |> yLine(/*yAbs*/ endAbsolute = 5 + 0) - |> yLine(/*yRel*/ length = 3.14 + 0, tag = $a) - |> xLine(/*xRel*/ length = 3.14 + 0) - |> /*1*/ angledLine(angle = 3 + 0, lengthX = 3.14 + 0) - |> /*2*/ angledLine(angle = 30 + 0, lengthY = 3 + 0) - |> /*3*/ angledLine(angle = 12.14 + 0, endAbsoluteX = 12 + 0) - |> /*4*/ angledLine(angle = 30 + 0, endAbsoluteY = 10.14 + 0) - |> angledLineThatIntersects(angle = 3.14 + 0, intersectTag = a, offset = 0 + 0) - |> tangentialArc(endAbsolute = [3.14 + 0, 13.14 + 0])` - const cases: [string, InputArg['type'], number | string, string][] = [ - [' line(end = [3 + 0, 4])', 'arrayItem', 1, ''], - [ - '/*0*/ angledLine(angle = 3, length = 3.14 + 0)', - 'labeledArg', - 'angle', - '', - ], - ['line(endAbsolute = [6.14 + 0, 3.14 + 0])', 'arrayItem', 0, ''], - ['xLine(endAbsolute = 8)', 'singleValue', '', '/*xAbs*/'], - ['yLine(endAbsolute = 5)', 'singleValue', '', '/*yAbs*/'], - ['yLine(length = 3.14, tag = $a)', 'singleValue', '', '/*yRel*/'], - ['xLine(length = 3.14)', 'singleValue', '', '/*xRel*/'], - [ - '/*1*/ angledLine(angle = 3, lengthX = 3.14 + 0)', - 'labeledArg', - 'angle', - '', - ], - [ - '/*2*/ angledLine(angle = 30 + 0, lengthY = 3)', - 'labeledArg', - 'length', - '', - ], - [ - '/*3*/ angledLine(angle = 12.14 + 0, endAbsoluteX = 12)', - 'labeledArg', - 'endAbsoluteX', - '', - ], - [ - '/*4*/ angledLine(angle = 30, endAbsoluteY = 10.14 + 0)', - 'labeledArg', - 'angle', - '', - ], - [ - `angledLineThatIntersects(angle = 3.14 + 0, intersectTag = a, offset = 0)`, - 'labeledArg', - 'offset', - '', - ], - [ - 'tangentialArc(endAbsolute = [3.14 + 0, 13.14])', - 'labeledArgArrayItem', - 'endAbsolute', - '', - ], - ] - test.each(cases)( - 'stdlib fn: %s', - async (expectedFinish, key, value, commentLabel) => { - const ast = assertParse(code) - - const execState = await enginelessExecutor(ast) - const lineOfInterest = - commentLabel.length > 0 - ? expectedFinish.split(commentLabel)[0] - : expectedFinish.split('(')[0] + '(' - const start = code.indexOf(lineOfInterest) - const range = topLevelRange(start + 1, start + lineOfInterest.length) - const pathToNode = getNodePathFromSourceRange(ast, range) - let argPosition: SimplifiedArgDetails - if (key === 'arrayItem' && typeof value === 'number') { - argPosition = { - type: 'arrayItem', - index: value === 0 ? 0 : 1, - } - } else if (key === 'singleValue') { - argPosition = { - type: 'singleValue', - } - } else if (key === 'labeledArg' && typeof value === 'string') { - argPosition = { - type: 'labeledArg', - key: value as any, - } - } else if (key === 'labeledArgArrayItem') { - argPosition = { - type: 'labeledArgArrayItem', - key: value as any, - index: 1, - } - } else { - throw new Error('argPosition is undefined') - } - const mod = removeSingleConstraintInfo( - pathToNode, - argPosition, - ast, - execState.variables - ) - if (!mod) return new Error('mod is undefined') - const recastCode = recast(mod.modifiedAst) - expect(recastCode).toContain(expectedFinish) - } - ) - }) - describe('with array notation', () => { - const code = `part001 = startSketchOn(-XZ) - |> startProfile(at = [0, 0]) - |> /*0*/ angledLine(angle = 3.14 + 0, length = 3.14 + 0) - |> /*1*/ angledLine(angle = 3 + 0, lengthX = 3.14 + 0) - |> /*2*/ angledLine(angle = 30 + 0, lengthY = 3 + 0) - |> /*3*/ angledLine(angle = 12.14 + 0, endAbsoluteX = 12 + 0) - |> /*4*/ angledLine(angle = 30 + 0, endAbsoluteY = 10.14 + 0)` - const ang: InputArgKeys = 'angle' - test.each([ - ['/*0*/ angledLine(angle = 3, length = 3.14 + 0)', 'labeledArg', ang], - [ - '/*1*/ angledLine(angle = 3, lengthX = 3.14 + 0)', - 'labeledArg', - 'angle', - ], - [ - '/*2*/ angledLine(angle = 30 + 0, lengthY = 3)', - 'labeledArg', - 'lengthY', - ], - [ - '/*3*/ angledLine(angle = 12.14 + 0, endAbsoluteX = 12)', - 'labeledArg', - 'endAbsoluteX', - ], - [ - '/*4*/ angledLine(angle = 30, endAbsoluteY = 10.14 + 0)', - 'labeledArg', - 'angle', - ], - ])('stdlib fn: %s', async (expectedFinish, key, value) => { - const ast = assertParse(code) - - const execState = await enginelessExecutor(ast) - const lineOfInterest = expectedFinish.split('(')[0] + '(' - const start = code.indexOf(lineOfInterest) - expect(start).toBeGreaterThanOrEqual(0) - const range = topLevelRange(start + 1, start + lineOfInterest.length) - let argPosition: SimplifiedArgDetails - if (key === 'arrayIndex' && typeof value === 'number') { - argPosition = { - type: 'arrayItem', - index: value === 0 ? 0 : 1, - } - } else if (key === 'labeledArg') { - argPosition = { - type: 'labeledArg', - key: value as InputArgKeys, - } - } else { - throw new Error('argPosition is undefined') - } - const pathToNode = getNodePathFromSourceRange(ast, range) - const mod = removeSingleConstraintInfo( - pathToNode, - argPosition, - ast, - execState.variables - ) - if (!mod) return new Error('mod is undefined') - const recastCode = recast(mod.modifiedAst) - expect(recastCode).toContain(expectedFinish) - }) - }) -}) - describe('Testing deleteFromSelection', () => { const cases = [ [ diff --git a/src/lang/std/sketch.test.ts b/src/lang/std/sketch.test.ts index 9ee5fe42c..d1b09f39a 100644 --- a/src/lang/std/sketch.test.ts +++ b/src/lang/std/sketch.test.ts @@ -320,7 +320,11 @@ describe('testing getConstraintInfo', () => { isConstrained: false, value: '6.14', sourceRange: [expect.any(Number), expect.any(Number), 0], - argPosition: { type: 'arrayItem', index: 0 }, + argPosition: { + type: 'labeledArgArrayItem', + key: 'endAbsolute', + index: 0, + }, pathToNode: expect.any(Array), stdLibFnName: 'line', }, @@ -329,7 +333,11 @@ describe('testing getConstraintInfo', () => { isConstrained: false, value: '3.14', sourceRange: [expect.any(Number), expect.any(Number), 0], - argPosition: { type: 'arrayItem', index: 1 }, + argPosition: { + type: 'labeledArgArrayItem', + key: 'endAbsolute', + index: 1, + }, pathToNode: expect.any(Array), stdLibFnName: 'line', }, @@ -352,7 +360,7 @@ describe('testing getConstraintInfo', () => { isConstrained: false, value: '8', sourceRange: [expect.any(Number), expect.any(Number), 0], - argPosition: { type: 'singleValue' }, + argPosition: { type: 'labeledArg', key: 'endAbsolute' }, pathToNode: expect.any(Array), stdLibFnName: 'xLineTo', }, @@ -375,7 +383,7 @@ describe('testing getConstraintInfo', () => { isConstrained: false, value: '5', sourceRange: [expect.any(Number), expect.any(Number), 0], - argPosition: { type: 'singleValue' }, + argPosition: { type: 'labeledArg', key: 'endAbsolute' }, pathToNode: expect.any(Array), stdLibFnName: 'yLineTo', }, @@ -437,7 +445,7 @@ describe('testing getConstraintInfo', () => { sourceRange: [expect.any(Number), expect.any(Number), 0], argPosition: { type: 'labeledArg', key: 'angle' }, pathToNode: expect.any(Array), - stdLibFnName: 'angledLineOfXLength', + stdLibFnName: 'angledLine', }, { type: 'xRelative', @@ -446,7 +454,7 @@ describe('testing getConstraintInfo', () => { sourceRange: [expect.any(Number), expect.any(Number), 0], argPosition: { type: 'labeledArg', key: 'lengthX' }, pathToNode: expect.any(Array), - stdLibFnName: 'angledLineOfXLength', + stdLibFnName: 'angledLine', }, ], ], @@ -668,7 +676,7 @@ describe('testing getConstraintInfo', () => { sourceRange: [expect.any(Number), expect.any(Number), 0], argPosition: { type: 'labeledArg', key: 'angle' }, pathToNode: expect.any(Array), - stdLibFnName: 'angledLineOfXLength', + stdLibFnName: 'angledLine', }, { type: 'xRelative', @@ -677,7 +685,7 @@ describe('testing getConstraintInfo', () => { sourceRange: [expect.any(Number), expect.any(Number), 0], argPosition: { type: 'labeledArg', key: 'lengthX' }, pathToNode: expect.any(Array), - stdLibFnName: 'angledLineOfXLength', + stdLibFnName: 'angledLine', }, ], ], @@ -845,7 +853,11 @@ describe('testing getConstraintInfo', () => { isConstrained: true, value: '6.14 + 0', sourceRange: [expect.any(Number), expect.any(Number), 0], - argPosition: { type: 'arrayItem', index: 0 }, + argPosition: { + type: 'labeledArgArrayItem', + key: 'endAbsolute', + index: 0, + }, pathToNode: expect.any(Array), stdLibFnName: 'line', }, @@ -854,7 +866,11 @@ describe('testing getConstraintInfo', () => { isConstrained: true, value: '3.14 + 0', sourceRange: [expect.any(Number), expect.any(Number), 0], - argPosition: { type: 'arrayItem', index: 1 }, + argPosition: { + type: 'labeledArgArrayItem', + key: 'endAbsolute', + index: 1, + }, pathToNode: expect.any(Array), stdLibFnName: 'line', }, @@ -877,7 +893,7 @@ describe('testing getConstraintInfo', () => { isConstrained: true, value: '8 + 0', sourceRange: [expect.any(Number), expect.any(Number), 0], - argPosition: { type: 'singleValue' }, + argPosition: { type: 'labeledArg', key: 'endAbsolute' }, pathToNode: expect.any(Array), stdLibFnName: 'xLineTo', }, @@ -900,7 +916,7 @@ describe('testing getConstraintInfo', () => { isConstrained: true, value: '5 + 0', sourceRange: [expect.any(Number), expect.any(Number), 0], - argPosition: { type: 'singleValue' }, + argPosition: { type: 'labeledArg', key: 'endAbsolute' }, pathToNode: expect.any(Array), stdLibFnName: 'yLineTo', }, @@ -962,7 +978,7 @@ describe('testing getConstraintInfo', () => { sourceRange: [expect.any(Number), expect.any(Number), 0], argPosition: { type: 'labeledArg', key: 'angle' }, pathToNode: expect.any(Array), - stdLibFnName: 'angledLineOfXLength', + stdLibFnName: 'angledLine', }, { type: 'xRelative', @@ -971,7 +987,7 @@ describe('testing getConstraintInfo', () => { sourceRange: [expect.any(Number), expect.any(Number), 0], argPosition: { type: 'labeledArg', key: 'lengthX' }, pathToNode: expect.any(Array), - stdLibFnName: 'angledLineOfXLength', + stdLibFnName: 'angledLine', }, ], ], diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index f03241153..8f360d4ba 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -86,9 +86,8 @@ import { err } from '@src/lib/trap' import { allLabels, getAngle, getLength, roundOff } from '@src/lib/utils' import type { EdgeCutInfo } from '@src/machines/modelingMachine' -const STRAIGHT_SEGMENT_ERR = new Error( - 'Invalid input, expected "straight-segment"' -) +const STRAIGHT_SEGMENT_ERR = () => + new Error('Invalid input, expected "straight-segment"') const ARC_SEGMENT_ERR = () => new Error('Invalid input, expected "arc-segment"') const CIRCLE_THREE_POINT_SEGMENT_ERR = new Error( 'Invalid input, expected "circle-three-point-segment"' @@ -134,7 +133,9 @@ const constrainInfo = ( ? { type: 'objectProperty', key: g } : g?.type === 'labeledArg' ? g - : undefined + : g?.type === 'labeledArgArrayItem' + ? g + : undefined return { type: a, @@ -365,7 +366,7 @@ const horzVertConstraintInfoHelper = ( if (argIndex === undefined) { return [] } - const firstArg = callExp.arguments?.[argIndex].arg + const mainArg = callExp.arguments?.[argIndex].arg const callee = callExp.callee const pathToFirstArg: PathToNode = [ ...pathToNode, @@ -389,11 +390,11 @@ const horzVertConstraintInfoHelper = ( ), constrainInfo( inputConstrainTypes[1], - isNotLiteralArrayOrStatic(firstArg), - code.slice(firstArg.start, firstArg.end), + isNotLiteralArrayOrStatic(mainArg), + code.slice(mainArg.start, mainArg.end), stdLibFnName, abbreviatedInput, - topLevelRange(firstArg.start, firstArg.end), + topLevelRange(mainArg.start, mainArg.end), pathToFirstArg ), ] @@ -420,7 +421,7 @@ export const line: SketchLineHelperKw = { replaceExistingCallback, spliceBetween, }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( @@ -520,7 +521,7 @@ export const line: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -559,7 +560,7 @@ export const lineTo: SketchLineHelperKw = { replaceExistingCallback, spliceBetween, }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const to = segmentInput.to const _node = structuredClone(node) const nodeMeta = getNodeFromPath( @@ -618,15 +619,17 @@ export const lineTo: SketchLineHelperKw = { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const result = replaceExistingCallback([ { - type: 'arrayItem', + type: 'labeledArgArrayItem', + key: ARG_END_ABSOLUTE, index: 0, - argType: 'xRelative', + argType: 'xAbsolute', expr: newXVal, }, { - type: 'arrayItem', + type: 'labeledArgArrayItem', + key: ARG_END_ABSOLUTE, index: 1, - argType: 'yRelative', + argType: 'yAbsolute', expr: newYVal, }, ]) @@ -669,7 +672,7 @@ export const lineTo: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -689,19 +692,58 @@ export const lineTo: SketchLineHelperKw = { }, getTag: getTagKwArg(), addTag: addTagKw(), - getConstraintInfo: (callExp, ...args) => - commonConstraintInfoHelper( - callExp, - ['xAbsolute', 'yAbsolute'], - 'line', - [{ arrayInput: 0 }, { arrayInput: 1 }], - ...args - ), + + getConstraintInfo: (callExp, code, pathToNode) => { + const endAbsoluteArg = findKwArgWithIndex(ARG_END_ABSOLUTE, callExp) + if (endAbsoluteArg === undefined) { + return [] + } + const { expr, argIndex } = endAbsoluteArg + const constraints: ConstrainInfo[] = [] + if (!(expr.type === 'ArrayExpression' && expr.elements.length === 2)) { + return [] + } + const pipeExpressionIndex = pathToNode.findIndex( + ([_, nodeName]) => nodeName === 'PipeExpression' + ) + const pathToArg: PathToNode = [ + ...pathToNode.slice(0, pipeExpressionIndex + 2), + ['arguments', 'CallExpressionKw'], + [argIndex, ARG_INDEX_FIELD], + ['arg', LABELED_ARG_FIELD], + ['elements', 'ArrayExpression'], + ] + const pathToXArg: PathToNode = [...pathToArg, [0, 'index']] + const pathToYArg: PathToNode = [...pathToArg, [1, 'index']] + constraints.push( + constrainInfo( + 'xAbsolute', + isNotLiteralArrayOrStatic(expr.elements[0]), + code.slice(expr.elements[0].start, expr.elements[0].end), + 'line', + { type: 'labeledArgArrayItem', index: 0, key: ARG_END_ABSOLUTE }, + topLevelRange(expr.elements[0].start, expr.elements[0].end), + pathToXArg + ) + ) + constraints.push( + constrainInfo( + 'yAbsolute', + isNotLiteralArrayOrStatic(expr.elements[1]), + code.slice(expr.elements[1].start, expr.elements[1].end), + 'line', + { type: 'labeledArgArrayItem', index: 1, key: ARG_END_ABSOLUTE }, + topLevelRange(expr.elements[1].start, expr.elements[1].end), + pathToYArg + ) + ) + return constraints + }, } export const xLineTo: SketchLineHelperKw = { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) @@ -714,7 +756,8 @@ export const xLineTo: SketchLineHelperKw = { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const result = replaceExistingCallback([ { - type: 'singleValue', + type: 'labeledArg', + key: ARG_END_ABSOLUTE, argType: 'xAbsolute', expr: createLiteral(roundOff(to[0], 2)), }, @@ -738,7 +781,7 @@ export const xLineTo: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -759,14 +802,14 @@ export const xLineTo: SketchLineHelperKw = { callExp, ['horizontal', 'xAbsolute'], 'xLineTo', - 'singleValue', + { type: 'labeledArg', key: ARG_END_ABSOLUTE }, ...args ), } export const yLineTo: SketchLineHelperKw = { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) @@ -780,7 +823,8 @@ export const yLineTo: SketchLineHelperKw = { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const result = replaceExistingCallback([ { - type: 'singleValue', + type: 'labeledArg', + key: ARG_END_ABSOLUTE, argType: 'yAbsolute', expr: newVal, }, @@ -804,7 +848,7 @@ export const yLineTo: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -825,14 +869,14 @@ export const yLineTo: SketchLineHelperKw = { callExp, ['vertical', 'yAbsolute'], 'yLineTo', - 'singleValue', + { type: 'labeledArg', key: ARG_END_ABSOLUTE }, ...args ), } export const xLine: SketchLineHelperKw = { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { from, to } = segmentInput const _node = structuredClone(node) const getNode = getNodeFromPathCurry(_node, pathToNode) @@ -876,7 +920,7 @@ export const xLine: SketchLineHelperKw = { return { modifiedAst: _node, pathToNode } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -904,7 +948,7 @@ export const xLine: SketchLineHelperKw = { export const yLine: SketchLineHelperKw = { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { from, to } = segmentInput const _node = structuredClone(node) const getNode = getNodeFromPathCurry(_node, pathToNode) @@ -946,7 +990,7 @@ export const yLine: SketchLineHelperKw = { return { modifiedAst: _node, pathToNode } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -974,7 +1018,7 @@ export const yLine: SketchLineHelperKw = { export const tangentialArc: SketchLineHelperKw = { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) @@ -1046,7 +1090,7 @@ export const tangentialArc: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -1190,7 +1234,7 @@ export const tangentialArc: SketchLineHelperKw = { export const startProfile: SketchLineHelperKw = { updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -1211,8 +1255,7 @@ export const startProfile: SketchLineHelperKw = { getTag: getTagKwArg(), addTag: addTagKw(), add: ({ node, pathToNode, replaceExistingCallback, segmentInput }) => { - console.log('segmentInput', segmentInput) - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = segmentInput const _node = structuredClone(node) const nodeMeta = getNodeFromPath( @@ -2136,7 +2179,7 @@ export const arcTo: SketchLineHelperKw = { interiorAbsoluteArr.elements[0].start, interiorAbsoluteArr.elements[0].end ), - stdLibFnName: 'arc', + stdLibFnName: 'arcTo', argPosition: { type: 'labeledArgArrayItem', key: ARG_INTERIOR_ABSOLUTE, @@ -2158,7 +2201,7 @@ export const arcTo: SketchLineHelperKw = { interiorAbsoluteArr.elements[1].start, interiorAbsoluteArr.elements[1].end ), - stdLibFnName: 'arc', + stdLibFnName: 'arcTo', argPosition: { type: 'labeledArgArrayItem', key: ARG_INTERIOR_ABSOLUTE, @@ -2175,7 +2218,7 @@ export const arcTo: SketchLineHelperKw = { type: 'xAbsolute', isConstrained: isNotLiteralArrayOrStatic(endArr.elements[0]), value: code.slice(endArr.elements[0].start, endArr.elements[0].end), - stdLibFnName: 'arc', + stdLibFnName: 'arcTo', argPosition: { type: 'labeledArgArrayItem', key: 'endAbsolute', @@ -2192,7 +2235,7 @@ export const arcTo: SketchLineHelperKw = { type: 'yAbsolute', isConstrained: isNotLiteralArrayOrStatic(endArr.elements[1]), value: code.slice(endArr.elements[1].start, endArr.elements[1].end), - stdLibFnName: 'arc', + stdLibFnName: 'arcTo', argPosition: { type: 'labeledArgArrayItem', key: 'endAbsolute', @@ -2242,42 +2285,42 @@ export const circleThreePoint: SketchLineHelperKw = { if (replaceExistingCallback) { const result = replaceExistingCallback([ { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 0, key: 'p1', argType: 'xAbsolute', expr: createRoundedLiteral(p1[0]), }, { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 1, key: 'p1', argType: 'yAbsolute', expr: createRoundedLiteral(p1[1]), }, { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 0, key: 'p2', argType: 'xAbsolute', expr: createRoundedLiteral(p2[0]), }, { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 1, key: 'p2', argType: 'yAbsolute', expr: createRoundedLiteral(p2[1]), }, { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 0, key: 'p3', argType: 'xAbsolute', expr: createRoundedLiteral(p3[0]), }, { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 1, key: 'p3', argType: 'yAbsolute', @@ -2344,21 +2387,21 @@ export const circleThreePoint: SketchLineHelperKw = { ...pathToNode, ['arguments', 'CallExpressionKw'], [p1Details.argIndex, 'arg index'], - ['arg', 'labeledArg -> Arg'], + ['arg', LABELED_ARG_FIELD], ['elements', 'ArrayExpression'], ] const pathToP2ArrayExpression: PathToNode = [ ...pathToNode, ['arguments', 'CallExpressionKw'], [p2Details.argIndex, 'arg index'], - ['arg', 'labeledArg -> Arg'], + ['arg', LABELED_ARG_FIELD], ['elements', 'ArrayExpression'], ] const pathToP3ArrayExpression: PathToNode = [ ...pathToNode, ['arguments', 'CallExpressionKw'], [p3Details.argIndex, 'arg index'], - ['arg', 'labeledArg -> Arg'], + ['arg', LABELED_ARG_FIELD], ['elements', 'ArrayExpression'], ] @@ -2385,7 +2428,7 @@ export const circleThreePoint: SketchLineHelperKw = { p1Details.expr.elements[0].end ), argPosition: { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 0, key: 'p1', }, @@ -2406,7 +2449,7 @@ export const circleThreePoint: SketchLineHelperKw = { p1Details.expr.elements[1].end ), argPosition: { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 1, key: 'p1', }, @@ -2427,7 +2470,7 @@ export const circleThreePoint: SketchLineHelperKw = { p2Details.expr.elements[0].end ), argPosition: { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 0, key: 'p2', }, @@ -2448,7 +2491,7 @@ export const circleThreePoint: SketchLineHelperKw = { p2Details.expr.elements[1].end ), argPosition: { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 1, key: 'p2', }, @@ -2469,7 +2512,7 @@ export const circleThreePoint: SketchLineHelperKw = { p3Details.expr.elements[0].end ), argPosition: { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 0, key: 'p3', }, @@ -2490,7 +2533,7 @@ export const circleThreePoint: SketchLineHelperKw = { p3Details.expr.elements[1].end ), argPosition: { - type: 'arrayInObject', + type: 'labeledArgArrayItem', index: 1, key: 'p3', }, @@ -2512,7 +2555,7 @@ export const circleThreePoint: SketchLineHelperKw = { export const angledLine: SketchLineHelperKw = { add: ({ node, pathToNode, segmentInput, replaceExistingCallback, snaps }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { from, to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) @@ -2585,7 +2628,7 @@ export const angledLine: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -2628,7 +2671,7 @@ export const angledLineOfXLength: SketchLineHelperKw = { segmentInput, replaceExistingCallback, }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( @@ -2657,16 +2700,14 @@ export const angledLineOfXLength: SketchLineHelperKw = { if (replaceExistingCallback) { const result = replaceExistingCallback([ { - type: 'arrayOrObjItem', - index: 0, + type: 'labeledArg', key: 'angle', argType: 'angle', expr: angle, }, { - type: 'arrayOrObjItem', - index: 1, - key: 'length', + type: 'labeledArg', + key: 'lengthX', argType: 'xRelative', expr: xLength, }, @@ -2691,7 +2732,7 @@ export const angledLineOfXLength: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -2730,7 +2771,7 @@ export const angledLineOfXLength: SketchLineHelperKw = { commonConstraintInfoHelper( callExp, ['angle', 'xRelative'], - 'angledLineOfXLength', + 'angledLine', [{ argLabel: 'angle' }, { argLabel: 'lengthX' }], ...args ), @@ -2744,7 +2785,7 @@ export const angledLineOfYLength: SketchLineHelperKw = { segmentInput, replaceExistingCallback, }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( @@ -2771,16 +2812,14 @@ export const angledLineOfYLength: SketchLineHelperKw = { if (replaceExistingCallback) { const result = replaceExistingCallback([ { - type: 'arrayOrObjItem', - index: 0, + type: 'labeledArg', key: 'angle', argType: 'angle', expr: angle, }, { - type: 'arrayOrObjItem', - index: 1, - key: 'length', + type: 'labeledArg', + key: 'lengthY', argType: 'yRelative', expr: yLength, }, @@ -2805,7 +2844,7 @@ export const angledLineOfYLength: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -2852,7 +2891,7 @@ export const angledLineOfYLength: SketchLineHelperKw = { export const angledLineToX: SketchLineHelperKw = { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( @@ -2868,16 +2907,14 @@ export const angledLineToX: SketchLineHelperKw = { if (replaceExistingCallback) { const result = replaceExistingCallback([ { - type: 'arrayOrObjItem', - index: 0, + type: 'labeledArg', key: 'angle', argType: 'angle', expr: angle, }, { - type: 'arrayOrObjItem', - index: 1, - key: 'to', + type: 'labeledArg', + key: 'endAbsoluteX', argType: 'xAbsolute', expr: xArg, }, @@ -2904,7 +2941,7 @@ export const angledLineToX: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -2941,7 +2978,7 @@ export const angledLineToX: SketchLineHelperKw = { export const angledLineToY: SketchLineHelperKw = { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( @@ -2959,16 +2996,14 @@ export const angledLineToY: SketchLineHelperKw = { if (replaceExistingCallback) { const result = replaceExistingCallback([ { - type: 'arrayOrObjItem', - index: 0, + type: 'labeledArg', key: 'angle', argType: 'angle', expr: angle, }, { - type: 'arrayOrObjItem', - index: 1, - key: 'to', + type: 'labeledArg', + key: 'endAbsoluteY', argType: 'yAbsolute', expr: yArg, }, @@ -2995,7 +3030,7 @@ export const angledLineToY: SketchLineHelperKw = { } }, updateArgs: ({ node, pathToNode, input }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -3038,7 +3073,7 @@ export const angledLineThatIntersects: SketchLineHelperKw = { replaceExistingCallback, referencedSegment, }) => { - if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( @@ -3094,7 +3129,7 @@ export const angledLineThatIntersects: SketchLineHelperKw = { return new Error('not implemented') }, updateArgs: ({ node, pathToNode, input, variables }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -3215,7 +3250,7 @@ export const updateStartProfileAtArgs: SketchLineHelperKw['updateArgs'] = ({ pathToNode, input, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR() const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) @@ -3370,6 +3405,7 @@ export function fnNameToToolTipFromSegment( case 'tangentialArc': case 'angledLine': case 'startProfile': + case 'arcTo': return fnName default: const err = `Unknown sketch line function ${fnName}` diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index cf725528d..255704099 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1350,8 +1350,15 @@ export function removeSingleConstraint({ return false } + const toolTip = fnNameToTooltip( + allLabels(callExp.node), + callExp.node.callee.name.name + ) + const transform: TransformInfo = { - tooltip: callExp.node.callee.name.name as any, + tooltip: err(toolTip) + ? (callExp.node.callee.name.name as ToolTip) + : toolTip, createNode: ({ tag, inputs, rawArgs }) => { // inputs is the current values for each of the inputs // rawValues is the raw 'literal' values equivalent to the inputs diff --git a/src/machines/modelingMachine.test.ts b/src/machines/modelingMachine.test.ts index 181257a9a..eabf4469e 100644 --- a/src/machines/modelingMachine.test.ts +++ b/src/machines/modelingMachine.test.ts @@ -4,7 +4,7 @@ import { } from '@src/machines/modelingMachine' import { createActor } from 'xstate' import { vi } from 'vitest' -import { assertParse, type CallExpressionKw } from '@src/lang/wasm' +import { assertParse, recast, type CallExpressionKw } from '@src/lang/wasm' import { initPromise } from '@src/lang/wasmUtils' import { codeManager, @@ -12,7 +12,7 @@ import { kclManager, } from '@src/lib/singletons' import { VITE_KC_DEV_TOKEN } from '@src/env' -import { line } from '@src/lang/std/sketch' +import { getConstraintInfoKw } 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' @@ -21,6 +21,8 @@ import { createLiteral, createVariableDeclaration, } from '@src/lang/create' +import { ARG_END_ABSOLUTE, ARG_INTERIOR_ABSOLUTE } from '@src/lang/constants' +import { removeSingleConstraintInfo } from '@src/lang/modifyAst' // Store original method to restore in afterAll @@ -114,132 +116,1186 @@ describe('modelingMachine - XState', () => { }) }) - describe('when in sketch mode', () => { - it('should transition to sketch state when entering sketch mode', async () => { - const code = `sketch001 = startSketchOn(XZ) + const makeStraightSegmentSnippet = (line: string) => ({ + code: `testVar1 = 55 +testVar2 = 66 +testVar3 = 77 +testVar4 = 88 +sketch001 = startSketchOn(XZ) profile001 = startProfile(sketch001, at = [2263.04, -2721.2]) - |> line(end = [16.27, 73.81]) - |> line(end = [75.72, 18.41]) -` +|> line(end = [78, 19]) +|> ${line} +|> line(end = [75.72, 18.41])`, + searchText: line, + }) + const makeCircSnippet = (line: string) => ({ + code: `testVar1 = 55 +testVar2 = 66 +testVar3 = 77 +testVar4 = 88 +testVar5 = 99 +testVar6 = 11 +sketch001 = startSketchOn(YZ) +profile001 = ${line} +`, + searchText: line, + }) + const threePointCirceCode = `circleThreePoint( +sketch001, +p1 = [testVar1, testVar2], +p2 = [testVar3, testVar4], +p3 = [testVar5, testVar6], +)` + const threePointCirceCodeLiterals = `circleThreePoint( +sketch001, +p1 = [281.18, 215.74], +p2 = [295.39, 269.96], +p3 = [342.51, 216.38], +)` - const ast = assertParse(code) + type TestDetails = { + name: string + code: string + searchText: string + constraintIndex: number + expectedResult: string + filter?: string + } - await kclManager.executeAst({ ast }) + const cases: { + [strLibName: string]: { + namedConstantConstraint: TestDetails[] + removeAllConstraintsCases: TestDetails[] + removeIndividualConstraintsCases: TestDetails[] + deleteSegment: Omit[] + } + } = { + line: { + namedConstantConstraint: [ + { + name: 'should constrain line x value', + ...makeStraightSegmentSnippet('line(end = [16.27, 73.81])'), + constraintIndex: 0, + expectedResult: 'line(end = [test_variable,', + }, + { + name: 'should constrain line y value', + ...makeStraightSegmentSnippet('line(end = [16.27, 73.81])'), + constraintIndex: 1, + expectedResult: 'line(end = [16.27, test_variable]', + }, + { + name: 'should constrain line absolute x value', + ...makeStraightSegmentSnippet('line(endAbsolute = [16.27, 73.81])'), + constraintIndex: 0, + expectedResult: 'line(endAbsolute = [test_variable, 73.81]', + }, + { + name: 'should constrain line absolute y value', + ...makeStraightSegmentSnippet('line(endAbsolute = [16.27, 73.81])'), + constraintIndex: 1, + expectedResult: 'line(endAbsolute = [16.27, test_variable]', + }, + ], + removeAllConstraintsCases: [ + { + name: 'should un-constrain rel-line', + ...makeStraightSegmentSnippet('line(end = [testVar1, testVar2])'), + constraintIndex: 0, + expectedResult: 'line(end = [55, 66])', + }, + { + name: 'should un-constrain abs-line', + ...makeStraightSegmentSnippet( + 'line(endAbsolute = [testVar1, testVar2])' + ), + constraintIndex: 0, + expectedResult: 'line(end = [-2286.04, 2768.2]', + // TODO un-constrains to relative line when it should not, expectedResult should be the following + // expectedResult: 'line(endAbsolute = [55, 66]', + }, + ], + removeIndividualConstraintsCases: [ + { + name: 'should un-constrain line x value', + ...makeStraightSegmentSnippet('line(end = [testVar1, testVar2])'), + constraintIndex: 0, + expectedResult: 'line(end = [55, testVar2]', + }, + { + name: 'should un-constrain line y value', + ...makeStraightSegmentSnippet('line(end = [testVar1, testVar2])'), + constraintIndex: 1, + expectedResult: 'line(end = [testVar1, 66]', + }, + { + name: 'should un-constrain line absolute x value', + ...makeStraightSegmentSnippet( + 'line(endAbsolute = [testVar1, testVar2])' + ), + constraintIndex: 0, + // expectedResult: 'line(end = [-2286.04, testVar2])', + // // TODO should not swap from abs to relative, expected should be + expectedResult: 'line(endAbsolute = [55, testVar2]', + }, + { + name: 'should un-constrain line absolute y value', + ...makeStraightSegmentSnippet( + 'line(endAbsolute = [testVar1, testVar2])' + ), + constraintIndex: 1, + expectedResult: 'line(endAbsolute = [testVar1, 66])', + }, + ], + deleteSegment: [ + { + name: 'should delete rel-line', + ...makeStraightSegmentSnippet('line(end = [testVar1, testVar2])'), + }, + { + name: 'should delete abs-line', + ...makeStraightSegmentSnippet( + 'line(endAbsolute = [testVar1, testVar2])' + ), + }, + ], + }, + xLine: { + namedConstantConstraint: [ + { + name: 'should constrain xLine x value', + ...makeStraightSegmentSnippet('xLine(length = 15)'), + constraintIndex: 1, + expectedResult: 'xLine(length = test_variable)', + }, + { + name: 'should constrain xLine x absolute value', + ...makeStraightSegmentSnippet('xLine(endAbsolute = 15)'), + constraintIndex: 1, + expectedResult: 'xLine(endAbsolute = test_variable)', + }, + ], + removeAllConstraintsCases: [ + { + name: 'should un-constrain xLine', + ...makeStraightSegmentSnippet('xLine(length = testVar1)'), + constraintIndex: 1, + expectedResult: 'line(end = [55, 0])', + }, + { + name: 'should un-constrain xLine absolute value', + ...makeStraightSegmentSnippet('xLine(endAbsolute = testVar1)'), + constraintIndex: 1, + expectedResult: 'line(end = [-2286.04, 0])', + // TODO un-constrains to relative line when it should not, expectedResult should be the following + // expectedResult: 'line(endAbsolute = [55, 0])', + }, + ], + removeIndividualConstraintsCases: [ + { + name: 'should un-constrain xLine x value', + ...makeStraightSegmentSnippet('xLine(length = testVar1)'), + constraintIndex: 1, + expectedResult: 'xLine(length = 55)', + }, + { + name: 'should un-constrain xLine x absolute value', + ...makeStraightSegmentSnippet('xLine(endAbsolute = testVar1)'), + constraintIndex: 1, + expectedResult: 'xLine(endAbsolute = 55)', + }, + ], + deleteSegment: [ + { + name: 'should delete xLine', + ...makeStraightSegmentSnippet('xLine(length = 15)'), + }, + { + name: 'should delete xLine', + ...makeStraightSegmentSnippet('xLine(endAbsolute = 15)'), + }, + ], + }, + yLine: { + namedConstantConstraint: [ + { + name: 'should constrain yLine y value', + ...makeStraightSegmentSnippet('yLine(length = 15)'), + constraintIndex: 1, + expectedResult: 'yLine(length = test_variable)', + }, + { + name: 'should constrain yLine y absolute value', + ...makeStraightSegmentSnippet('yLine(endAbsolute = 15)'), + constraintIndex: 1, + expectedResult: 'yLine(endAbsolute = test_variable)', + }, + ], + removeAllConstraintsCases: [ + { + name: 'should un-constrain yLine value', + ...makeStraightSegmentSnippet('yLine(length = testVar1)'), + constraintIndex: 1, + expectedResult: 'line(end = [0, 55])', + }, + { + name: 'should un-constrain yLine absolute value', + ...makeStraightSegmentSnippet('yLine(endAbsolute = testVar1)'), + constraintIndex: 1, + expectedResult: 'line(end = [0, 2757.2])', + // TODO un-constrains to relative line when it should not, expectedResult should be the following + // expectedResult: 'line(endAbsolute = [0, 55])', + }, + ], + removeIndividualConstraintsCases: [ + { + name: 'should un-constrain yLine y value', + ...makeStraightSegmentSnippet('yLine(length = testVar1)'), + constraintIndex: 1, + expectedResult: 'yLine(length = 55)', + }, + { + name: 'should un-constrain yLine y absolute value', + ...makeStraightSegmentSnippet('yLine(endAbsolute = testVar1)'), + constraintIndex: 1, + expectedResult: 'yLine(endAbsolute = 55)', + }, + ], + deleteSegment: [ + { + name: 'should delete yLine', + ...makeStraightSegmentSnippet('yLine(length = 15)'), + }, + { + name: 'should delete yLine abs', + ...makeStraightSegmentSnippet('yLine(endAbsolute = 15)'), + }, + ], + }, + angledLine: { + namedConstantConstraint: [ + { + name: 'should constrain angledLine, angle value', + ...makeStraightSegmentSnippet('angledLine(angle = 45, length = 100)'), + constraintIndex: 0, + expectedResult: 'angledLine(angle = test_variable, length = 100)', + }, + { + name: 'should constrain angledLine, length value', + ...makeStraightSegmentSnippet('angledLine(angle = 45, length = 100)'), + constraintIndex: 1, + expectedResult: 'angledLine(angle = 45, length = test_variable)', + }, + { + name: 'should constrain angledLine, endAbsoluteY value', + ...makeStraightSegmentSnippet( + 'angledLine(angle = 45, endAbsoluteY = 5)' + ), + constraintIndex: 1, + expectedResult: + 'angledLine(angle = 45, endAbsoluteY = test_variable)', + }, + { + name: 'should constrain angledLine, endAbsoluteX value', + ...makeStraightSegmentSnippet( + 'angledLine(angle = 45, endAbsoluteX = 5)' + ), + constraintIndex: 1, + expectedResult: + 'angledLine(angle = 45, endAbsoluteX = test_variable)', + }, + { + name: 'should constrain angledLine, lengthY value', + ...makeStraightSegmentSnippet('angledLine(angle = 45, lengthY = 5)'), + constraintIndex: 1, + expectedResult: 'angledLine(angle = 45, lengthY = test_variable)', + }, + { + name: 'should constrain angledLine, lengthX value', + ...makeStraightSegmentSnippet('angledLine(angle = 45, lengthX = 5)'), + constraintIndex: 1, + expectedResult: 'angledLine(angle = 45, lengthX = test_variable)', + }, + ], + removeAllConstraintsCases: [ + { + name: 'should un-constrain angledLine', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, length = testVar2)' + ), + constraintIndex: 0, + expectedResult: 'line(end = [37.86, 54.06]', + }, + { + name: 'should un-constrain angledLine, endAbsoluteY', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, endAbsoluteY = testVar2)' + ), + constraintIndex: 1, + expectedResult: 'line(end = [1938.31, 2768.2])', + }, + { + name: 'should un-constrain angledLine, endAbsoluteX', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, endAbsoluteX = testVar2)' + ), + constraintIndex: 1, + expectedResult: 'line(end = [-2275.04, -3249.09])', + }, + { + name: 'should un-constrain angledLine, lengthY', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, lengthY = testVar2)' + ), + constraintIndex: 1, + expectedResult: 'line(end = [46.21, 66])', + }, + { + name: 'should un-constrain angledLine, lengthX', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, lengthX = testVar2)' + ), + constraintIndex: 1, + expectedResult: 'line(end = [66, 94.26])', + }, + ], + removeIndividualConstraintsCases: [ + { + name: 'should un-constrain angledLine, angle value', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, length = testVar2)' + ), + constraintIndex: 0, + expectedResult: 'angledLine(angle = 55, length = testVar2)', + }, + { + name: 'should un-constrain angledLine, length value', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, length = testVar2)' + ), + constraintIndex: 1, + expectedResult: 'angledLine(angle = testVar1, length = 66)', + }, + { + name: 'should un-constrain angledLine, endAbsoluteY value', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, endAbsoluteY = testVar2)' + ), + constraintIndex: 1, + expectedResult: 'angledLine(angle = testVar1, endAbsoluteY = 66)', + }, + { + name: 'should un-constrain angledLine, endAbsoluteX value', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, endAbsoluteX = testVar2)' + ), + constraintIndex: 1, + expectedResult: 'angledLine(angle = testVar1, endAbsoluteX = 66)', + }, + { + name: 'should un-constrain angledLine, lengthY value', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, lengthY = testVar2)' + ), + constraintIndex: 1, + expectedResult: 'angledLine(angle = testVar1, lengthY = 66)', + }, + { + name: 'should un-constrain angledLine, lengthX value', + ...makeStraightSegmentSnippet( + 'angledLine(angle = testVar1, lengthX = testVar2)' + ), + constraintIndex: 1, + expectedResult: 'angledLine(angle = testVar1, lengthX = 66)', + }, + ], + deleteSegment: [ + { + name: 'should delete angledLine, angle length', + ...makeStraightSegmentSnippet('angledLine(angle = 45, length = 100)'), + }, + { + name: 'should delete angledLine, endAbsoluteY', + ...makeStraightSegmentSnippet( + 'angledLine(angle = 45, endAbsoluteY = 5)' + ), + }, + { + name: 'should delete angledLine, endAbsoluteX', + ...makeStraightSegmentSnippet( + 'angledLine(angle = 45, endAbsoluteX = 5)' + ), + }, + { + name: 'should delete angledLine, lengthY', + ...makeStraightSegmentSnippet('angledLine(angle = 45, lengthY = 5)'), + }, + { + name: 'should delete angledLine, lengthX', + ...makeStraightSegmentSnippet('angledLine(angle = 45, lengthX = 5)'), + }, + ], + }, + circle: { + namedConstantConstraint: [ + { + name: 'should constrain circle, radius value', + ...makeCircSnippet( + 'circle(sketch001, center = [140.82, 183.92], radius = 74.18)' + ), + constraintIndex: 0, + expectedResult: + 'circle(sketch001, center = [140.82, 183.92], radius = test_variable)', + }, + { + name: 'should constrain circle, center x value', + ...makeCircSnippet( + 'circle(sketch001, center = [140.82, 183.92], radius = 74.18)' + ), + constraintIndex: 1, + expectedResult: + 'circle(sketch001, center = [test_variable, 183.92], radius = 74.18)', + }, + { + name: 'should constrain circle, center y value', + ...makeCircSnippet( + 'circle(sketch001, center = [140.82, 183.92], radius = 74.18)' + ), + constraintIndex: 2, + expectedResult: + 'circle(sketch001, center = [140.82, test_variable], radius = 74.18)', + }, + ], + removeAllConstraintsCases: [ + // TODO circle when remove all is working + ], + removeIndividualConstraintsCases: [ + { + name: 'should un-constrain circle, radius value', + ...makeCircSnippet( + 'circle(sketch001, center = [140.82, 183.92], radius = testVar1)' + ), + constraintIndex: 0, + expectedResult: + 'circle(sketch001, center = [140.82, 183.92], radius = 55)', + }, + { + name: 'should un-constrain circle, center x value', + ...makeCircSnippet( + 'circle(sketch001, center = [testVar1, testVar2], radius = 74.18)' + ), + constraintIndex: 1, + expectedResult: + 'circle(sketch001, center = [55, testVar2], radius = 74.18)', + }, + { + name: 'should un-constrain circle, center y value', + ...makeCircSnippet( + 'circle(sketch001, center = [testVar1, testVar2], radius = 74.18)' + ), + constraintIndex: 2, + expectedResult: + 'circle(sketch001, center = [testVar1, 66], radius = 74.18)', + }, + ], + deleteSegment: [ + /** TODO once circle has delete implemented */ + ], + }, + circleThreePoint: { + namedConstantConstraint: [ + { + name: 'should constrain circleThreePoint, p1 x value', + ...makeCircSnippet(threePointCirceCodeLiterals), + constraintIndex: 0, + expectedResult: 'p1 = [test_variable, 215.74]', + filter: 'p1', + }, + { + name: 'should constrain circleThreePoint, p1 y value', + ...makeCircSnippet(threePointCirceCodeLiterals), + constraintIndex: 1, + expectedResult: 'p1 = [281.18, test_variable]', + filter: 'p1', + }, + { + name: 'should constrain circleThreePoint, p2 x value', + ...makeCircSnippet(threePointCirceCodeLiterals), + constraintIndex: 0, + expectedResult: 'p2 = [test_variable, 269.96]', + filter: 'p2', + }, + { + name: 'should constrain circleThreePoint, p2 y value', + ...makeCircSnippet(threePointCirceCodeLiterals), + constraintIndex: 1, + expectedResult: 'p2 = [295.39, test_variable]', + filter: 'p2', + }, + { + name: 'should constrain circleThreePoint, p3 x value', + ...makeCircSnippet(threePointCirceCodeLiterals), + constraintIndex: 0, + expectedResult: 'p3 = [test_variable, 216.38]', + filter: 'p3', + }, + { + name: 'should constrain circleThreePoint, p3 y value', + ...makeCircSnippet(threePointCirceCodeLiterals), + constraintIndex: 1, + expectedResult: 'p3 = [342.51, test_variable]', + filter: 'p3', + }, + ], + removeAllConstraintsCases: [ + // TODO circleThreePoint when remove all is working + ], + removeIndividualConstraintsCases: [ + { + name: 'should un-constrain circleThreePoint, p1 x value', + ...makeCircSnippet(threePointCirceCode), + constraintIndex: 0, + expectedResult: 'p1 = [55, testVar2]', + filter: 'p1', + }, + { + name: 'should un-constrain circleThreePoint, p1 y value', + ...makeCircSnippet(threePointCirceCode), + constraintIndex: 1, + expectedResult: 'p1 = [testVar1, 66]', + filter: 'p1', + }, + { + name: 'should un-constrain circleThreePoint, p2 x value', + ...makeCircSnippet(threePointCirceCode), + constraintIndex: 0, + expectedResult: 'p2 = [77, testVar4]', + filter: 'p2', + }, + { + name: 'should un-constrain circleThreePoint, p2 y value', + ...makeCircSnippet(threePointCirceCode), + constraintIndex: 1, + expectedResult: 'p2 = [testVar3, 88]', + filter: 'p2', + }, + { + name: 'should un-constrain circleThreePoint, p3 x value', + ...makeCircSnippet(threePointCirceCode), + constraintIndex: 0, + expectedResult: 'p3 = [99, testVar6]', + filter: 'p3', + }, + { + name: 'should un-constrain circleThreePoint, p3 y value', + ...makeCircSnippet(threePointCirceCode), + constraintIndex: 1, + expectedResult: 'p3 = [testVar5, 11]', + filter: 'p3', + }, + ], + deleteSegment: [ + /**TODO once three point circle has delete implemented */ + ], + }, + tangentialArc: { + namedConstantConstraint: [ + { + name: 'should constrain tangentialArc absolute x value', + ...makeStraightSegmentSnippet( + 'tangentialArc(endAbsolute = [176.11, 19.49])' + ), + constraintIndex: 1, + expectedResult: 'endAbsolute = [test_variable, 19.49]', + }, + { + name: 'should constrain tangentialArc absolute y value', + ...makeStraightSegmentSnippet( + 'tangentialArc(endAbsolute = [176.11, 19.49])' + ), + constraintIndex: 2, + expectedResult: 'endAbsolute = [176.11, test_variable]', + }, + // TODO tangentialArc relative when that's working + ], + removeAllConstraintsCases: [ + // TODO tangentialArc when remove all is working + ], + removeIndividualConstraintsCases: [ + { + name: 'should un-constrain tangentialArc absolute x value', + ...makeStraightSegmentSnippet( + 'tangentialArc(endAbsolute = [testVar1, testVar2])' + ), + constraintIndex: 1, + expectedResult: 'endAbsolute = [55, testVar2]', + }, + { + name: 'should un-constrain tangentialArc absolute y value', + ...makeStraightSegmentSnippet( + 'tangentialArc(endAbsolute = [testVar1, testVar2])' + ), + constraintIndex: 2, + expectedResult: 'endAbsolute = [testVar1, 66]', + }, + ], + deleteSegment: [ + { + name: 'should delete tangentialArc absolute', + ...makeStraightSegmentSnippet( + 'tangentialArc(endAbsolute = [176.11, 19.49])' + ), + }, + ], + }, + arc: { + namedConstantConstraint: [ + { + name: 'should constrain threePoint Arc interior x value', + ...makeStraightSegmentSnippet( + 'arc(interiorAbsolute = [379.93, 103.92], endAbsolute = [386.2, 162.89])' + ), + constraintIndex: 0, + expectedResult: 'interiorAbsolute = [test_variable, 103.92]', + filter: ARG_INTERIOR_ABSOLUTE, + }, + { + name: 'should constrain threePoint Arc interior y value', + ...makeStraightSegmentSnippet( + 'arc(interiorAbsolute = [379.93, 103.92], endAbsolute = [386.2, 162.89])' + ), + constraintIndex: 1, + expectedResult: 'interiorAbsolute = [379.93, test_variable]', + filter: ARG_INTERIOR_ABSOLUTE, + }, + { + name: 'should constrain threePoint Arc end x value', + ...makeStraightSegmentSnippet( + 'arc(interiorAbsolute = [379.93, 103.92], endAbsolute = [386.2, 162.89])' + ), + constraintIndex: 0, + expectedResult: 'endAbsolute = [test_variable, 162.89]', + filter: ARG_END_ABSOLUTE, + }, + { + name: 'should constrain threePoint Arc end y value', + ...makeStraightSegmentSnippet( + 'arc(interiorAbsolute = [379.93, 103.92], endAbsolute = [386.2, 162.89])' + ), + constraintIndex: 1, + expectedResult: 'endAbsolute = [386.2, test_variable]', + filter: ARG_END_ABSOLUTE, + }, + // TODO do other kwargs for arc + ], + removeAllConstraintsCases: [ + // TODO arc when remove all is working + ], + removeIndividualConstraintsCases: [ + { + name: 'should un-constrain threePoint Arc interior x value', + ...makeStraightSegmentSnippet( + 'arc(interiorAbsolute = [testVar1, testVar2], endAbsolute = [testVar3, testVar4])' + ), + constraintIndex: 0, + expectedResult: 'interiorAbsolute = [55, testVar2]', + filter: ARG_INTERIOR_ABSOLUTE, + }, + { + name: 'should un-constrain threePoint Arc interior y value', + ...makeStraightSegmentSnippet( + 'arc(interiorAbsolute = [testVar1, testVar2], endAbsolute = [testVar3, testVar4])' + ), + constraintIndex: 1, + expectedResult: 'interiorAbsolute = [testVar1, 66]', + filter: ARG_INTERIOR_ABSOLUTE, + }, + { + name: 'should un-constrain threePoint Arc end x value', + ...makeStraightSegmentSnippet( + 'arc(interiorAbsolute = [testVar1, testVar2], endAbsolute = [testVar3, testVar4])' + ), + constraintIndex: 0, + expectedResult: 'endAbsolute = [77, testVar4]', + filter: ARG_END_ABSOLUTE, + }, + { + name: 'should un-constrain threePoint Arc end y value', + ...makeStraightSegmentSnippet( + 'arc(interiorAbsolute = [testVar1, testVar2], endAbsolute = [testVar3, testVar4])' + ), + constraintIndex: 1, + expectedResult: 'endAbsolute = [testVar3, 88]', + filter: ARG_END_ABSOLUTE, + }, + ], + deleteSegment: [ + { + name: 'should delete threePoint Arc (interior, end)', + ...makeStraightSegmentSnippet( + 'arc(interiorAbsolute = [379.93, 103.92], endAbsolute = [386.2, 162.89])' + ), + }, + ], + }, + } - expect(kclManager.errors).toEqual([]) + describe('Deleting segment with three dot menu', () => { + const namedConstantConstraintCases = Object.values(cases).flatMap( + (caseGroup) => caseGroup.deleteSegment + ) + namedConstantConstraintCases.forEach( + ({ name, code, searchText, filter }) => { + it(name, async () => { + const indexOfInterest = code.indexOf(searchText) - const indexOfInterest = code.indexOf('[16.27, 73.81]') + const ast = assertParse(code) - // 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') - } + await kclManager.executeAst({ ast }) - const actor = createActor(modelingMachine, { - input: modelingMachineDefaultContext, - }).start() + expect(kclManager.errors).toEqual([]) - // Send event to transition to sketch mode - actor.send({ - type: 'Set selection', - data: { - selectionType: 'mirrorCodeMirrorSelections', - selection: { - graphSelections: [ - { - artifact: artifact, - codeRef: artifact.codeRef, + // 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: [], }, - ], - 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>( - 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) + actor.send({ type: 'Enter sketch' }) - 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) + // 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 callExp = getNodeFromPath>( + kclManager.ast, + artifact.codeRef.pathToNode, + 'CallExpressionKw' + ) + if (err(callExp)) { + throw new Error('Failed to get CallExpressionKw node') + } + const constraintInfo = getConstraintInfoKw( + callExp.node, + codeManager.code, + artifact.codeRef.pathToNode, + filter + ) + const constraint = constraintInfo[0] + + // Now that we're in sketchIdle state, test the "Constrain with named value" event + actor.send({ + type: 'Delete segment', + data: constraint.pathToNode, + }) + + // 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) + + const startTime = Date.now() + while ( + codeManager.code.includes(searchText) && + Date.now() - startTime < 5000 + ) { + await new Promise((resolve) => setTimeout(resolve, 100)) + } + expect(codeManager.code).not.toContain(searchText) + }, 10_000) + } + ) + }) + describe('Adding segment overlay constraints', () => { + const namedConstantConstraintCases = Object.values(cases).flatMap( + (caseGroup) => caseGroup.namedConstantConstraint + ) + namedConstantConstraintCases.forEach( + ({ name, code, searchText, constraintIndex, expectedResult, filter }) => { + it(name, async () => { + const indexOfInterest = code.indexOf(searchText) + + const ast = assertParse(code) + + await kclManager.executeAst({ ast }) + + expect(kclManager.errors).toEqual([]) + + // 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 callExp = getNodeFromPath>( + kclManager.ast, + artifact.codeRef.pathToNode, + 'CallExpressionKw' + ) + if (err(callExp)) { + throw new Error('Failed to get CallExpressionKw node') + } + const constraintInfo = getConstraintInfoKw( + callExp.node, + codeManager.code, + artifact.codeRef.pathToNode, + filter + ) + const constraint = constraintInfo[constraintIndex] + + // Now that we're in sketchIdle state, test the "Constrain with named value" event + actor.send({ + type: 'Constrain with named value', + data: { + currentValue: { + valueText: constraint.value, + pathToNode: constraint.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(expectedResult) + }, 10_000) + } + ) + }) + describe('removing individual constraints with segment overlay events', () => { + const removeIndividualConstraintsCases = Object.values(cases).flatMap( + (caseGroup) => caseGroup.removeIndividualConstraintsCases + ) + + removeIndividualConstraintsCases.forEach( + ({ name, code, searchText, constraintIndex, expectedResult, filter }) => { + it(name, async () => { + const indexOfInterest = code.indexOf(searchText) + + const ast = assertParse(code) + + await kclManager.executeAst({ ast }) + + expect(kclManager.errors).toEqual([]) + + // 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 callExp = getNodeFromPath>( + kclManager.ast, + artifact.codeRef.pathToNode, + 'CallExpressionKw' + ) + if (err(callExp)) { + throw new Error('Failed to get CallExpressionKw node') + } + const constraintInfo = getConstraintInfoKw( + callExp.node, + codeManager.code, + artifact.codeRef.pathToNode, + filter + ) + const constraint = constraintInfo[constraintIndex] + console.log('constraint', constraint) + if (!constraint.argPosition) { + throw new Error( + `Constraint at index ${constraintIndex} does not have argPosition` + ) + } + + const mod = removeSingleConstraintInfo( + constraint.pathToNode, + constraint.argPosition, + ast, + kclManager.variables + ) + if (!mod) { + throw new Error('Failed to remove constraint info') + } + const codeRecast = recast(mod.modifiedAst) + + expect(codeRecast).toContain(expectedResult) + }, 10_000) + } + ) + }) + describe('Removing segment overlay constraints', () => { + const removeAllConstraintsCases = Object.values(cases).flatMap( + (caseGroup) => caseGroup.removeAllConstraintsCases + ) + + removeAllConstraintsCases.forEach( + ({ name, code, searchText, constraintIndex, expectedResult, filter }) => { + it(name, async () => { + const indexOfInterest = code.indexOf(searchText) + + const ast = assertParse(code) + + await kclManager.executeAst({ ast }) + + expect(kclManager.errors).toEqual([]) + + // 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 callExp = getNodeFromPath>( + kclManager.ast, + artifact.codeRef.pathToNode, + 'CallExpressionKw' + ) + if (err(callExp)) { + throw new Error('Failed to get CallExpressionKw node') + } + const constraintInfo = getConstraintInfoKw( + callExp.node, + codeManager.code, + artifact.codeRef.pathToNode, + filter + ) + const constraint = constraintInfo[constraintIndex] + + // Now that we're in sketchIdle state, test the "Constrain with named value" event + actor.send({ + type: 'Constrain remove constraints', + data: constraint.pathToNode, + }) + + // 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: 'Constrain remove constraints' }) + ) + }, 5000) + const startTime = Date.now() + while ( + !codeManager.code.includes(expectedResult) && + Date.now() - startTime < 5000 + ) { + await new Promise((resolve) => setTimeout(resolve, 100)) + } + expect(codeManager.code).toContain(expectedResult) + }, 10_000) + } + ) }) })