* separate handling of tangentialArc with angle and radius args * make previousEndTangent available in segment input for handling tangentialArc with angle/radius * start adding support for editing tangentialArc with angle, radius * draw tangentialArc sketch when using angle, radius * fix getTanPreviousPoint when using tangentialArc with angle, radius * fix case of unwanted negative angles when calculating angle for tangentialArc * lint * add test for tangentialArc dragging with andle, radius * lint, fmt * fix getArgForEnd for tangentialArc with radius, angle * renaming vars
		
			
				
	
	
		
			3769 lines
		
	
	
		
			122 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			3769 lines
		
	
	
		
			122 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import fs from 'node:fs/promises'
 | 
						|
import path from 'node:path'
 | 
						|
import type { Page } from '@playwright/test'
 | 
						|
import { roundOff, uuidv4 } from '@src/lib/utils'
 | 
						|
 | 
						|
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
 | 
						|
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
 | 
						|
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
 | 
						|
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
 | 
						|
import {
 | 
						|
  PERSIST_MODELING_CONTEXT,
 | 
						|
  TEST_COLORS,
 | 
						|
  getMovementUtils,
 | 
						|
  getUtils,
 | 
						|
} from '@e2e/playwright/test-utils'
 | 
						|
import { expect, test } from '@e2e/playwright/zoo-test'
 | 
						|
 | 
						|
test.describe('Sketch tests', () => {
 | 
						|
  test('multi-sketch file shows multiple Edit Sketch buttons', async ({
 | 
						|
    page,
 | 
						|
    context,
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    const selectionsSnippets = {
 | 
						|
      startProfileAt1:
 | 
						|
        '|> startProfile(at = [-width / 4 + screwRadius, height / 2])',
 | 
						|
      startProfileAt2: '|> startProfile(at = [-width / 2, 0])',
 | 
						|
      startProfileAt3: '|> startProfile(at = [0, thickness])',
 | 
						|
    }
 | 
						|
    await context.addInitScript(
 | 
						|
      async ({ startProfileAt1, startProfileAt2, startProfileAt3 }: any) => {
 | 
						|
        localStorage.setItem(
 | 
						|
          'persistCode',
 | 
						|
          `
 | 
						|
    width = 20
 | 
						|
    height = 10
 | 
						|
    thickness = 5
 | 
						|
    screwRadius = 3
 | 
						|
    wireRadius = 2
 | 
						|
    wireOffset = 0.5
 | 
						|
 | 
						|
    screwHole = startSketchOn(XY)
 | 
						|
  ${startProfileAt1}
 | 
						|
  |> arc(angleStart = 0, angleEnd = 360, radius = screwRadius)
 | 
						|
 | 
						|
    part001 = startSketchOn(XY)
 | 
						|
  ${startProfileAt2}
 | 
						|
  |> xLine(length = width * .5)
 | 
						|
  |> yLine(length = height)
 | 
						|
  |> xLine(length = -width * .5)
 | 
						|
  |> close()
 | 
						|
  |> subtract2d(tool = screwHole)
 | 
						|
  |> extrude(length = thickness)
 | 
						|
 | 
						|
  part002 = startSketchOn(-XZ)
 | 
						|
  ${startProfileAt3}
 | 
						|
  |> xLine(length = width / 4)
 | 
						|
  |> tangentialArc(endAbsolute = [width / 2, 0])
 | 
						|
  |> xLine(length = -width / 4 + wireRadius)
 | 
						|
  |> yLine(length = wireOffset)
 | 
						|
  |> arc(angleStart = 0, angleEnd = 180, radius = wireRadius)
 | 
						|
  |> yLine(length = -wireOffset)
 | 
						|
  |> xLine(length = -width / 4)
 | 
						|
  |> close()
 | 
						|
  |> extrude(length = -height)
 | 
						|
    `
 | 
						|
        )
 | 
						|
      },
 | 
						|
      selectionsSnippets
 | 
						|
    )
 | 
						|
    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(selectionsSnippets.startProfileAt1).click()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Edit Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
 | 
						|
    await page.getByText(selectionsSnippets.startProfileAt2).click()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Edit Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
 | 
						|
    await page.getByText(selectionsSnippets.startProfileAt3).click()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Edit Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
  })
 | 
						|
  test('Can delete most of a sketch and the line tool will still work', async ({
 | 
						|
    page,
 | 
						|
    scene,
 | 
						|
    homePage,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
  |> startProfile(at = [2.61, -4.01])
 | 
						|
  |> xLine(length = 8.73)
 | 
						|
  |> tangentialArc(endAbsolute = [8.33, -1.31])`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 587, y: 270 }, 15)
 | 
						|
 | 
						|
    await expect(async () => {
 | 
						|
      await page.mouse.click(700, 200)
 | 
						|
      await page.getByText('tangentialArc(endAbsolute = [8.33, -1.31])').click()
 | 
						|
      await expect(
 | 
						|
        page.getByRole('button', { name: 'Edit Sketch' })
 | 
						|
      ).toBeEnabled({ timeout: 2000 })
 | 
						|
      await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
						|
    }).toPass({ timeout: 40_000, intervals: [1_000] })
 | 
						|
 | 
						|
    await page.waitForTimeout(600) // wait for animation
 | 
						|
 | 
						|
    await page.getByText('tangentialArc(endAbsolute = [8.33, -1.31])').click()
 | 
						|
    await page.keyboard.press('End')
 | 
						|
    await page.keyboard.down('Shift')
 | 
						|
    await page.keyboard.press('ArrowUp')
 | 
						|
    await page.keyboard.press('Home')
 | 
						|
    await page.keyboard.up('Shift')
 | 
						|
    await page.keyboard.press('Backspace')
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
 | 
						|
    await page.getByRole('button', { name: 'line Line', exact: true }).click()
 | 
						|
    await page.waitForTimeout(500)
 | 
						|
    // click start profileAt handle to continue profile
 | 
						|
    await page.mouse.click(702, 406, { delay: 500 })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await page.mouse.move(800, 150)
 | 
						|
 | 
						|
    await expect(async () => {
 | 
						|
      // click to add segment
 | 
						|
      await page.mouse.click(700, 200)
 | 
						|
 | 
						|
      await expect
 | 
						|
        .poll(u.normalisedEditorCode, { timeout: 1000 })
 | 
						|
        .toBe(`@settings(defaultLengthUnit = in)
 | 
						|
 | 
						|
 | 
						|
sketch002 = startSketchOn(XZ)
 | 
						|
sketch001 = startProfile(sketch002, at = [12.34, -12.34])
 | 
						|
  |> yLine(length = 12.34)
 | 
						|
 | 
						|
`)
 | 
						|
    }).toPass({ timeout: 5_000, intervals: [1_000] })
 | 
						|
  })
 | 
						|
 | 
						|
  test('Can exit selection of face', async ({ page, homePage }) => {
 | 
						|
    // Load the app with the code panes
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem('persistCode', ``)
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
 | 
						|
    await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Exit Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
 | 
						|
    await expect(page.getByText('select a plane or face')).toBeVisible()
 | 
						|
 | 
						|
    await page.keyboard.press('Escape')
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
  })
 | 
						|
  test('Can edit segments by dragging their handles', () => {
 | 
						|
    const doEditSegmentsByDraggingHandle = async (
 | 
						|
      page: Page,
 | 
						|
      homePage: HomePageFixture,
 | 
						|
      openPanes: string[],
 | 
						|
      scene: SceneFixture,
 | 
						|
      toolbar: ToolbarFixture,
 | 
						|
      cmdBar: CmdBarFixture
 | 
						|
    ) => {
 | 
						|
      // Load the app with the code panes
 | 
						|
      await page.addInitScript(async () => {
 | 
						|
        localStorage.setItem(
 | 
						|
          'persistCode',
 | 
						|
          `sketch001 = startSketchOn(XZ)
 | 
						|
      |> startProfile(at = [4.61, -14.01])
 | 
						|
      |> line(end = [12.73, -0.09])
 | 
						|
      |> tangentialArc(endAbsolute = [24.95, -5.38])
 | 
						|
      |> arc(interiorAbsolute = [20.18, -1.7], endAbsolute = [11.82, -1.16])
 | 
						|
      |> arc(angleStart = -89.36, angleEnd = 135.81, radius = 5.92)
 | 
						|
      |> close()`
 | 
						|
        )
 | 
						|
      })
 | 
						|
 | 
						|
      const u = await getUtils(page)
 | 
						|
      await homePage.goToModelingScene()
 | 
						|
      await scene.settled(cmdBar)
 | 
						|
 | 
						|
      await expect(
 | 
						|
        page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
      ).not.toBeDisabled()
 | 
						|
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await u.openAndClearDebugPanel()
 | 
						|
      await u.sendCustomCmd({
 | 
						|
        type: 'modeling_cmd_req',
 | 
						|
        cmd_id: uuidv4(),
 | 
						|
        cmd: {
 | 
						|
          type: 'default_camera_look_at',
 | 
						|
          vantage: { x: 0, y: -1250, z: 580 },
 | 
						|
          center: { x: 0, y: 0, z: 0 },
 | 
						|
          up: { x: 0, y: 0, z: 1 },
 | 
						|
        },
 | 
						|
      })
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await u.sendCustomCmd({
 | 
						|
        type: 'modeling_cmd_req',
 | 
						|
        cmd_id: uuidv4(),
 | 
						|
        cmd: {
 | 
						|
          type: 'default_camera_get_settings',
 | 
						|
        },
 | 
						|
      })
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await u.closeDebugPanel()
 | 
						|
 | 
						|
      // If we have the code pane open, we should see the code.
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn(XZ)
 | 
						|
      |> startProfile(at = [4.61, -14.01])
 | 
						|
      |> line(end = [12.73, -0.09])
 | 
						|
      |> tangentialArc(endAbsolute = [24.95, -5.38])
 | 
						|
      |> arc(interiorAbsolute = [20.18, -1.7], endAbsolute = [11.82, -1.16])
 | 
						|
      |> arc(angleStart = -89.36, angleEnd = 135.81, radius = 5.92)
 | 
						|
      |> close()
 | 
						|
`)
 | 
						|
      } else {
 | 
						|
        // Ensure we don't see the code.
 | 
						|
        await expect(u.codeLocator).not.toBeVisible()
 | 
						|
      }
 | 
						|
 | 
						|
      const startPX = [665, 458]
 | 
						|
 | 
						|
      const dragPX = 30
 | 
						|
      let prevContent = ''
 | 
						|
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        await page.getByText('startProfile(at = [4.61, -14.01])').click()
 | 
						|
      } else {
 | 
						|
        // Wait for the render.
 | 
						|
        await page.waitForTimeout(1000)
 | 
						|
        // Select the sketch
 | 
						|
        await page.mouse.click(700, 370)
 | 
						|
      }
 | 
						|
      await toolbar.editSketch()
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        prevContent = await page.locator('.cm-content').innerText()
 | 
						|
      }
 | 
						|
 | 
						|
      const step5 = { steps: 5 }
 | 
						|
 | 
						|
      await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
 | 
						|
 | 
						|
      // drag startProfileAt handle
 | 
						|
      await page.mouse.move(startPX[0], startPX[1])
 | 
						|
      await page.mouse.down()
 | 
						|
      await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
 | 
						|
      await page.mouse.up()
 | 
						|
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
        prevContent = await page.locator('.cm-content').innerText()
 | 
						|
      }
 | 
						|
 | 
						|
      // drag line handle
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
 | 
						|
      const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
 | 
						|
      await page.mouse.move(lineEnd.x - 5, lineEnd.y)
 | 
						|
      await page.mouse.down()
 | 
						|
      await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
 | 
						|
      await page.mouse.up()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
        prevContent = await page.locator('.cm-content').innerText()
 | 
						|
      }
 | 
						|
 | 
						|
      // drag tangentialArc handle
 | 
						|
      const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
 | 
						|
      await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
 | 
						|
      await page.mouse.down()
 | 
						|
      await page.mouse.move(tangentEnd.x + dragPX, tangentEnd.y - dragPX, step5)
 | 
						|
      await page.mouse.up()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
      }
 | 
						|
 | 
						|
      // drag arcTo interiorAbsolute handle (three point arc)
 | 
						|
      const arcToHandle = await u.getBoundingBox('[data-overlay-index="2"]')
 | 
						|
      await page.mouse.move(arcToHandle.x, arcToHandle.y - 5)
 | 
						|
      await page.mouse.down()
 | 
						|
      await page.mouse.move(
 | 
						|
        arcToHandle.x - dragPX,
 | 
						|
        arcToHandle.y + dragPX,
 | 
						|
        step5
 | 
						|
      )
 | 
						|
      await page.mouse.up()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
        prevContent = await page.locator('.cm-content').innerText()
 | 
						|
      }
 | 
						|
 | 
						|
      // drag arcTo end handle (three point arc)
 | 
						|
      const arcToEndHandle = await u.getBoundingBox('[data-overlay-index="3"]')
 | 
						|
      await page.mouse.move(arcToEndHandle.x, arcToEndHandle.y - 5)
 | 
						|
      await page.mouse.down()
 | 
						|
      await page.mouse.move(
 | 
						|
        arcToEndHandle.x - dragPX,
 | 
						|
        arcToEndHandle.y + dragPX,
 | 
						|
        step5
 | 
						|
      )
 | 
						|
      await page.mouse.up()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
        prevContent = await page.locator('.cm-content').innerText()
 | 
						|
      }
 | 
						|
 | 
						|
      // drag arc radius handle
 | 
						|
      const arcRadiusHandle = await u.getBoundingBox('[data-overlay-index="4"]')
 | 
						|
      await page.mouse.move(arcRadiusHandle.x, arcRadiusHandle.y - 5)
 | 
						|
      await page.mouse.down()
 | 
						|
      await page.mouse.move(
 | 
						|
        arcRadiusHandle.x - dragPX,
 | 
						|
        arcRadiusHandle.y + dragPX,
 | 
						|
        step5
 | 
						|
      )
 | 
						|
      await page.mouse.up()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
      }
 | 
						|
 | 
						|
      // drag arc center handle (we'll have to hardcode the position because it doesn't have a overlay near the handle)
 | 
						|
      const arcCenterHandle = { x: 745, y: 214 }
 | 
						|
      await page.mouse.move(arcCenterHandle.x, arcCenterHandle.y - 5)
 | 
						|
      await page.mouse.down()
 | 
						|
      await page.mouse.move(
 | 
						|
        arcCenterHandle.x - dragPX,
 | 
						|
        arcCenterHandle.y + dragPX,
 | 
						|
        step5
 | 
						|
      )
 | 
						|
      await page.mouse.up()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      if (openPanes.includes('code')) {
 | 
						|
        await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
      }
 | 
						|
 | 
						|
      // Open the code pane
 | 
						|
      await u.openKclCodePanel()
 | 
						|
 | 
						|
      // expect the code to have changed
 | 
						|
      await expect(
 | 
						|
        page.locator('.cm-content')
 | 
						|
      ).toHaveText(`sketch001 = startSketchOn(XZ)
 | 
						|
  |> startProfile(at = [6.44, -12.07])
 | 
						|
  |> line(end = [14.72, 1.97])
 | 
						|
  |> tangentialArc(endAbsolute = [26.92, -3.32])
 | 
						|
  |> arc(interiorAbsolute = [18.11, -3.73], endAbsolute = [9.77, -3.19])
 | 
						|
  |> arc(angleStart = -58.29, angleEnd = 161.17, radius = 3.75)
 | 
						|
  |> close()
 | 
						|
`)
 | 
						|
    }
 | 
						|
    test('code pane open at start-handles', async ({
 | 
						|
      page,
 | 
						|
      homePage,
 | 
						|
      scene,
 | 
						|
      toolbar,
 | 
						|
      cmdBar,
 | 
						|
    }) => {
 | 
						|
      // Load the app with the code panes
 | 
						|
      await page.addInitScript(async () => {
 | 
						|
        localStorage.setItem(
 | 
						|
          'store',
 | 
						|
          JSON.stringify({
 | 
						|
            state: {
 | 
						|
              openPanes: ['code'],
 | 
						|
            },
 | 
						|
            version: 0,
 | 
						|
          })
 | 
						|
        )
 | 
						|
      })
 | 
						|
      await doEditSegmentsByDraggingHandle(
 | 
						|
        page,
 | 
						|
        homePage,
 | 
						|
        ['code'],
 | 
						|
        scene,
 | 
						|
        toolbar,
 | 
						|
        cmdBar
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    test('code pane closed at start-handles', async ({
 | 
						|
      page,
 | 
						|
      homePage,
 | 
						|
      scene,
 | 
						|
      toolbar,
 | 
						|
      cmdBar,
 | 
						|
    }) => {
 | 
						|
      // Load the app with the code panes
 | 
						|
      await page.addInitScript(async (persistModelingContext) => {
 | 
						|
        localStorage.setItem(
 | 
						|
          persistModelingContext,
 | 
						|
          JSON.stringify({ openPanes: [] })
 | 
						|
        )
 | 
						|
      }, PERSIST_MODELING_CONTEXT)
 | 
						|
      await doEditSegmentsByDraggingHandle(
 | 
						|
        page,
 | 
						|
        homePage,
 | 
						|
        [],
 | 
						|
        scene,
 | 
						|
        toolbar,
 | 
						|
        cmdBar
 | 
						|
      )
 | 
						|
    })
 | 
						|
  })
 | 
						|
 | 
						|
  test('Can edit a circle center and radius by dragging its handles', async ({
 | 
						|
    page,
 | 
						|
    editor,
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit=in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
    |> circle(center = [4.61, -5.01], radius = 8)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await u.openAndClearDebugPanel()
 | 
						|
    await u.sendCustomCmd({
 | 
						|
      type: 'modeling_cmd_req',
 | 
						|
      cmd_id: uuidv4(),
 | 
						|
      cmd: {
 | 
						|
        type: 'default_camera_look_at',
 | 
						|
        vantage: { x: 0, y: -1250, z: 580 },
 | 
						|
        center: { x: 0, y: 0, z: 0 },
 | 
						|
        up: { x: 0, y: 0, z: 1 },
 | 
						|
      },
 | 
						|
    })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await u.sendCustomCmd({
 | 
						|
      type: 'modeling_cmd_req',
 | 
						|
      cmd_id: uuidv4(),
 | 
						|
      cmd: {
 | 
						|
        type: 'default_camera_get_settings',
 | 
						|
      },
 | 
						|
    })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
 | 
						|
    const startPX = [667, 325]
 | 
						|
 | 
						|
    const dragPX = 40
 | 
						|
 | 
						|
    await page.getByText('circle(center = [4.61, -5.01], radius = 8)').click()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Edit Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
    await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
						|
    await page.waitForTimeout(400)
 | 
						|
 | 
						|
    let prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
    await expect(page.getByTestId('segment-overlay')).toHaveCount(1)
 | 
						|
 | 
						|
    await test.step('drag circle center handle', async () => {
 | 
						|
      await page.dragAndDrop('#stream', '#stream', {
 | 
						|
        sourcePosition: { x: startPX[0], y: startPX[1] },
 | 
						|
        targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX },
 | 
						|
      })
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
 | 
						|
      await editor.expectEditor.not.toContain(prevContent)
 | 
						|
 | 
						|
      prevContent = await page.locator('.cm-content').innerText()
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('drag circle radius handle', async () => {
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
 | 
						|
      const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await page.dragAndDrop('#stream', '#stream', {
 | 
						|
        sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
 | 
						|
        targetPosition: { x: lineEnd.x + dragPX * 2, y: lineEnd.y + dragPX },
 | 
						|
      })
 | 
						|
      await editor.expectEditor.not.toContain(prevContent)
 | 
						|
      prevContent = await page.locator('.cm-content').innerText()
 | 
						|
    })
 | 
						|
 | 
						|
    // expect the code to have changed
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `sketch001 = startSketchOn(XZ)
 | 
						|
    |> circle(center = [7.26, -2.37], radius = 11.44)`,
 | 
						|
      { shouldNormalise: true }
 | 
						|
    )
 | 
						|
  })
 | 
						|
  test('Can edit a sketch that has been extruded in the same pipe', async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
    editor,
 | 
						|
    toolbar,
 | 
						|
    scene,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit=in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
  |> startProfile(at = [4.61, -10.01])
 | 
						|
  |> line(end = [12.73, -0.09])
 | 
						|
  |> tangentialArc(endAbsolute = [24.95, -0.38])
 | 
						|
  |> close()
 | 
						|
  |> extrude(length = 5)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await toolbar.waitForFeatureTreeToBeBuilt()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled({ timeout: 10_000 })
 | 
						|
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await u.openAndClearDebugPanel()
 | 
						|
    await u.sendCustomCmd({
 | 
						|
      type: 'modeling_cmd_req',
 | 
						|
      cmd_id: uuidv4(),
 | 
						|
      cmd: {
 | 
						|
        type: 'default_camera_look_at',
 | 
						|
        vantage: { x: 0, y: -1250, z: 580 },
 | 
						|
        center: { x: 0, y: 0, z: 0 },
 | 
						|
        up: { x: 0, y: 0, z: 1 },
 | 
						|
      },
 | 
						|
    })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await u.sendCustomCmd({
 | 
						|
      type: 'modeling_cmd_req',
 | 
						|
      cmd_id: uuidv4(),
 | 
						|
      cmd: {
 | 
						|
        type: 'default_camera_get_settings',
 | 
						|
      },
 | 
						|
    })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
 | 
						|
    const startPX = [665, 397]
 | 
						|
 | 
						|
    const dragPX = 40
 | 
						|
 | 
						|
    await page.getByText('startProfile(at = [4.61, -10.01])').click()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Edit Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
    await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
						|
    await page.waitForTimeout(400)
 | 
						|
    let prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
    await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
 | 
						|
 | 
						|
    // drag startProfileAt handle
 | 
						|
    await page.dragAndDrop('#stream', '#stream', {
 | 
						|
      sourcePosition: { x: startPX[0], y: startPX[1] },
 | 
						|
      targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
 | 
						|
    })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
    prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
    // drag line handle
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
 | 
						|
    const lineEnd = await u.getBoundingBox('[data-overlay-index="1"]')
 | 
						|
    await page.dragAndDrop('#stream', '#stream', {
 | 
						|
      sourcePosition: { x: lineEnd.x - 15, y: lineEnd.y },
 | 
						|
      targetPosition: { x: lineEnd.x, y: lineEnd.y + 15 },
 | 
						|
    })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
    prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
    // drag tangentialArc handle
 | 
						|
    const tangentEnd = await u.getBoundingBox('[data-overlay-index="0"]')
 | 
						|
    await page.dragAndDrop('#stream', '#stream', {
 | 
						|
      sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
 | 
						|
      targetPosition: {
 | 
						|
        x: tangentEnd.x,
 | 
						|
        y: tangentEnd.y - 15,
 | 
						|
      },
 | 
						|
    })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
 | 
						|
    // expect the code to have changed
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `sketch001 = startSketchOn(XZ)
 | 
						|
    |> startProfile(at = [7.12, -12.68])
 | 
						|
    |> line(end = [12.68, -1.09])
 | 
						|
    |> tangentialArc(endAbsolute = [24.95, -0.38])
 | 
						|
    |> close()
 | 
						|
    |> extrude(length = 5)`,
 | 
						|
      { shouldNormalise: true }
 | 
						|
    )
 | 
						|
  })
 | 
						|
 | 
						|
  test('Can edit a sketch that has been revolved in the same pipe', async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    editor,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit=in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
  |> startProfile(at = [4.61, -14.01])
 | 
						|
  |> line(end = [12.73, -0.09])
 | 
						|
  |> tangentialArc(endAbsolute = [24.95, -5.38])
 | 
						|
  |> close()
 | 
						|
  |> revolve(axis = X)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await u.openAndClearDebugPanel()
 | 
						|
    await u.sendCustomCmd({
 | 
						|
      type: 'modeling_cmd_req',
 | 
						|
      cmd_id: uuidv4(),
 | 
						|
      cmd: {
 | 
						|
        type: 'default_camera_look_at',
 | 
						|
        vantage: { x: 0, y: -1250, z: 580 },
 | 
						|
        center: { x: 0, y: 0, z: 0 },
 | 
						|
        up: { x: 0, y: 0, z: 1 },
 | 
						|
      },
 | 
						|
    })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await u.sendCustomCmd({
 | 
						|
      type: 'modeling_cmd_req',
 | 
						|
      cmd_id: uuidv4(),
 | 
						|
      cmd: {
 | 
						|
        type: 'default_camera_get_settings',
 | 
						|
      },
 | 
						|
    })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
 | 
						|
    const startPX = [665, 458]
 | 
						|
 | 
						|
    const dragPX = 30
 | 
						|
 | 
						|
    await page.getByText('startProfile(at = [4.61, -14.01])').click()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Edit Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
    await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
						|
    await page.waitForTimeout(400)
 | 
						|
    let prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
    const step5 = { steps: 5 }
 | 
						|
 | 
						|
    await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
 | 
						|
 | 
						|
    // drag startProfileAt handle
 | 
						|
    await page.mouse.move(startPX[0], startPX[1])
 | 
						|
    await page.mouse.down()
 | 
						|
    await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
 | 
						|
    await page.mouse.up()
 | 
						|
 | 
						|
    await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
    prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
    // drag line handle
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
 | 
						|
    const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
 | 
						|
    await page.mouse.move(lineEnd.x - 5, lineEnd.y)
 | 
						|
    await page.mouse.down()
 | 
						|
    await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
 | 
						|
    await page.mouse.up()
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
    prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
    // drag tangentialArc handle
 | 
						|
    const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
 | 
						|
    await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
 | 
						|
    await page.mouse.down()
 | 
						|
    await page.mouse.move(tangentEnd.x + dragPX, tangentEnd.y - dragPX, step5)
 | 
						|
    await page.mouse.up()
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
 | 
						|
    // expect the code to have changed
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `sketch001 = startSketchOn(XZ)
 | 
						|
  |> startProfile(at = [8.41, -9.97])
 | 
						|
  |> line(end = [12.73, -0.09])
 | 
						|
  |> line(end = [1.99, 2.06])
 | 
						|
  |> tangentialArc(endAbsolute = [24.95, -5.38])
 | 
						|
  |> close()
 | 
						|
  |> revolve(axis = X)`,
 | 
						|
      { shouldNormalise: true }
 | 
						|
    )
 | 
						|
  })
 | 
						|
  test('Can add multiple sketches', async ({ page, homePage }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
 | 
						|
    const viewportSize = { width: 1200, height: 500 }
 | 
						|
    await page.setBodyDimensions(viewportSize)
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await u.openDebugPanel()
 | 
						|
 | 
						|
    const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
 | 
						|
    const { toSU, toU, click00r } = getMovementUtils({ center, page })
 | 
						|
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
 | 
						|
    // click on "Start Sketch" button
 | 
						|
    await u.clearCommandLogs()
 | 
						|
    await u.doAndWaitForImageDiff(
 | 
						|
      () => page.getByRole('button', { name: 'Start Sketch' }).click(),
 | 
						|
      200
 | 
						|
    )
 | 
						|
 | 
						|
    let codeStr =
 | 
						|
      '@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XY)'
 | 
						|
 | 
						|
    await page.mouse.click(center.x, viewportSize.height * 0.55)
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
    await u.closeDebugPanel()
 | 
						|
    await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
 | 
						|
 | 
						|
    await click00r(0, 0)
 | 
						|
    codeStr += `profile001 = startProfile(sketch001, at = ${toSU([0, 0])})`
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
 | 
						|
    await click00r(50, 0)
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    codeStr += `  |> xLine(length = ${toU(50, 0)[0]})`
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
 | 
						|
    await click00r(0, 50)
 | 
						|
    codeStr += `  |> yLine(length = ${toU(0, 50)[1]})`
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
 | 
						|
    await click00r(-50, 0)
 | 
						|
    codeStr += `  |> xLine(length = ${toU(-50, 0)[0]})`
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
 | 
						|
    // exit the sketch, reset relative clicker
 | 
						|
    await click00r(undefined, undefined)
 | 
						|
    await u.openAndClearDebugPanel()
 | 
						|
    await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
    await page.waitForTimeout(250)
 | 
						|
    await u.clearCommandLogs()
 | 
						|
 | 
						|
    // start a new sketch
 | 
						|
    await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
						|
 | 
						|
    // when exiting the sketch above the camera is still looking down at XY,
 | 
						|
    // so selecting the plane again is a bit easier.
 | 
						|
    await page.mouse.click(center.x + 200, center.y + 100)
 | 
						|
    await page.waitForTimeout(600) // TODO detect animation ending, or disable animation
 | 
						|
    codeStr += 'sketch002 = startSketchOn(XY)'
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
    await u.closeDebugPanel()
 | 
						|
 | 
						|
    await click00r(30, 0)
 | 
						|
    codeStr += `profile002 = startProfile(sketch002, at = [2.03, 0])`
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
 | 
						|
    // TODO: I couldn't use `toSU` here because of some rounding error causing
 | 
						|
    // it to be off by 0.01
 | 
						|
    await click00r(30, 0)
 | 
						|
    codeStr += `  |> xLine(length = 2.04)`
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
 | 
						|
    await click00r(0, 30)
 | 
						|
    codeStr += `  |> yLine(length = -2.03)`
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
 | 
						|
    await click00r(-30, 0)
 | 
						|
    codeStr += `  |> xLine(length = -2.04)`
 | 
						|
    await expect(u.codeLocator).toHaveText(codeStr)
 | 
						|
 | 
						|
    await click00r(undefined, undefined)
 | 
						|
    await u.openAndClearDebugPanel()
 | 
						|
    await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
    await u.updateCamPosition([100, 100, 100])
 | 
						|
    await u.clearCommandLogs()
 | 
						|
  })
 | 
						|
  test.describe('Snap to close works (at any scale)', () => {
 | 
						|
    const doSnapAtDifferentScales = async (
 | 
						|
      page: any,
 | 
						|
      camPos: [number, number, number],
 | 
						|
      scale = 1
 | 
						|
    ) => {
 | 
						|
      const u = await getUtils(page)
 | 
						|
      await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
						|
 | 
						|
      await u.openDebugPanel()
 | 
						|
 | 
						|
      const code = `@settings(defaultLengthUnit = in)
 | 
						|
sketch001 = startSketchOn(-XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff(
 | 
						|
        scale * 34.8
 | 
						|
      )}])
 | 
						|
    |> xLine(length = ${roundOff(scale * 139.19)})
 | 
						|
    |> yLine(length = -${roundOff(scale * 139.2)})
 | 
						|
    |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
    |> close()`
 | 
						|
 | 
						|
      await expect(
 | 
						|
        page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
      ).not.toBeDisabled()
 | 
						|
      await expect(
 | 
						|
        page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
      ).toBeVisible()
 | 
						|
 | 
						|
      await u.clearCommandLogs()
 | 
						|
      await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
 | 
						|
      await u.openAndClearDebugPanel()
 | 
						|
      await u.updateCamPosition(camPos)
 | 
						|
      await u.closeDebugPanel()
 | 
						|
 | 
						|
      await page.mouse.move(0, 0)
 | 
						|
 | 
						|
      // select a plane
 | 
						|
      await page.mouse.move(700, 200, { steps: 10 })
 | 
						|
      await page.mouse.click(700, 200, { delay: 200 })
 | 
						|
      await expect(page.locator('.cm-content')).toHaveText(
 | 
						|
        `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(-XZ)`
 | 
						|
      )
 | 
						|
 | 
						|
      let prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
      const pointA = [700, 200]
 | 
						|
      const pointB = [900, 200]
 | 
						|
      const pointC = [900, 400]
 | 
						|
 | 
						|
      // draw three lines
 | 
						|
      await page.waitForTimeout(500)
 | 
						|
      await page.mouse.move(pointA[0], pointA[1], { steps: 10 })
 | 
						|
      await page.mouse.click(pointA[0], pointA[1], { delay: 200 })
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
      prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
      await page.mouse.move(pointB[0], pointB[1], { steps: 10 })
 | 
						|
      await page.mouse.click(pointB[0], pointB[1], { delay: 200 })
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
      prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
      await page.mouse.move(pointC[0], pointC[1], { steps: 10 })
 | 
						|
      await page.mouse.click(pointC[0], pointC[1], { delay: 200 })
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
      prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
      await page.mouse.move(pointA[0] - 12, pointA[1] + 12, { steps: 10 })
 | 
						|
      const pointNotQuiteA = [pointA[0] - 7, pointA[1] + 7]
 | 
						|
      await page.mouse.move(pointNotQuiteA[0], pointNotQuiteA[1], {
 | 
						|
        steps: 10,
 | 
						|
      })
 | 
						|
 | 
						|
      await page.mouse.click(pointNotQuiteA[0], pointNotQuiteA[1], {
 | 
						|
        delay: 200,
 | 
						|
      })
 | 
						|
      await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
 | 
						|
      prevContent = await page.locator('.cm-content').innerText()
 | 
						|
 | 
						|
      await expect
 | 
						|
        .poll(async () => {
 | 
						|
          const text = await page.locator('.cm-content').innerText()
 | 
						|
          return text.replace(/\s/g, '')
 | 
						|
        })
 | 
						|
        .toBe(code.replace(/\s/g, ''))
 | 
						|
 | 
						|
      // Assert the tool stays equipped after a profile is closed (ready for the next one)
 | 
						|
      await expect(
 | 
						|
        page.getByRole('button', { name: 'line Line', exact: true })
 | 
						|
      ).toHaveAttribute('aria-pressed', 'true')
 | 
						|
 | 
						|
      // exit sketch
 | 
						|
      await u.openAndClearDebugPanel()
 | 
						|
      await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
						|
      await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
      await u.removeCurrentCode()
 | 
						|
    }
 | 
						|
    test('[0, 100, 100]', async ({ page, homePage }) => {
 | 
						|
      await homePage.goToModelingScene()
 | 
						|
      await doSnapAtDifferentScales(page, [0, 100, 100], 0.01)
 | 
						|
    })
 | 
						|
 | 
						|
    test('[0, 10000, 10000]', async ({ page, homePage }) => {
 | 
						|
      await homePage.goToModelingScene()
 | 
						|
      await doSnapAtDifferentScales(page, [0, 10000, 10000])
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('exiting a close extrude, has the extrude button enabled ready to go', async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
    cmdBar,
 | 
						|
    toolbar,
 | 
						|
  }) => {
 | 
						|
    // this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `sketch001 = startSketchOn(XZ)
 | 
						|
    |> startProfile(at = [-0.45, 0.87])
 | 
						|
    |> line(end = [1.32, 0.38])
 | 
						|
    |> line(end = [1.02, -1.32], tag = $seg01)
 | 
						|
    |> line(end = [-1.01, -0.77])
 | 
						|
    |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
  `
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    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()
 | 
						|
 | 
						|
    // click profile in code
 | 
						|
    await page.getByText(`startProfile(at = [-0.45, 0.87])`).click()
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
 | 
						|
      { timeout: 10_000 }
 | 
						|
    )
 | 
						|
    // click edit sketch
 | 
						|
    await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
						|
    await page.waitForTimeout(600) // wait for animation
 | 
						|
 | 
						|
    // exit sketch
 | 
						|
    await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
						|
 | 
						|
    // expect extrude button to be enabled
 | 
						|
    await expect(toolbar.extrudeButton).not.toBeDisabled()
 | 
						|
 | 
						|
    // click extrude
 | 
						|
    await toolbar.extrudeButton.click()
 | 
						|
 | 
						|
    // sketch selection should already have been made.
 | 
						|
    // otherwise the cmdbar would be waiting for a selection.
 | 
						|
    await cmdBar.progressCmdBar()
 | 
						|
    await cmdBar.expectState({
 | 
						|
      stage: 'arguments',
 | 
						|
      currentArgKey: 'length',
 | 
						|
      currentArgValue: '5',
 | 
						|
      headerArguments: { Profiles: '1 profile', Length: '' },
 | 
						|
      highlightedHeaderArg: 'length',
 | 
						|
      commandName: 'Extrude',
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test("Existing sketch with bad code delete user's code", async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
  }) => {
 | 
						|
    // this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `sketch001 = startSketchOn(XZ)
 | 
						|
    |> startProfile(at = [-0.45, 0.87])
 | 
						|
    |> line(end = [1.32, 0.38])
 | 
						|
    |> line(end = [1.02, -1.32], tag = $seg01)
 | 
						|
    |> line(end = [-1.01, -0.77])
 | 
						|
    |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
    |> close()
 | 
						|
  extrude001 = extrude(sketch001, length = 5)
 | 
						|
  `
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
    await u.closeDebugPanel()
 | 
						|
 | 
						|
    await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
						|
 | 
						|
    // Click the end face of extrude001
 | 
						|
    await page.mouse.click(622, 355)
 | 
						|
 | 
						|
    // The click should generate a new sketch starting on the end face of extrude001
 | 
						|
    // signified by the implicit 'END' tag for that solid.
 | 
						|
    await page.waitForTimeout(800)
 | 
						|
    await page.getByText(`END)`).click()
 | 
						|
    await page.keyboard.press('End')
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
    await page.keyboard.type('  |>', { delay: 100 })
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
 | 
						|
 | 
						|
    await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
						|
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
 | 
						|
    await expect((await u.codeLocator.innerText()).replace(/\s/g, '')).toBe(
 | 
						|
      `sketch001 = startSketchOn(XZ)
 | 
						|
    |> startProfile(at = [-0.45, 0.87])
 | 
						|
    |> line(end = [1.32, 0.38])
 | 
						|
    |> line(end = [1.02, -1.32], tag = $seg01)
 | 
						|
    |> line(end = [-1.01, -0.77])
 | 
						|
    |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
    |> close()
 | 
						|
  extrude001 = extrude(sketch001, length = 5)
 | 
						|
  sketch002 = startSketchOn(extrude001, face = END)
 | 
						|
    |>
 | 
						|
  `.replace(/\s/g, '')
 | 
						|
    )
 | 
						|
  })
 | 
						|
  // TODO: fix after electron migration is merged
 | 
						|
  test('empty-scene default-planes act as expected', async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    cmdBar,
 | 
						|
    editor,
 | 
						|
  }) => {
 | 
						|
    /**
 | 
						|
     * Tests the following things
 | 
						|
     * 1) The the planes are there on load because the scene is empty
 | 
						|
     * 2) The planes don't changes color when hovered initially
 | 
						|
     * 3) Putting something in the scene makes the planes hidden
 | 
						|
     * 4) Removing everything from the scene shows the plans again
 | 
						|
     * 3) Once "start sketch" is click, the planes do respond to hovers
 | 
						|
     * 4) Selecting a plan works as expected, i.e. sketch mode
 | 
						|
     * 5) Reloading the scene with something already in the scene means the planes are hidden
 | 
						|
     */
 | 
						|
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
    await u.closeDebugPanel()
 | 
						|
 | 
						|
    const XYPlanePoint = { x: 774, y: 116 } as const
 | 
						|
    const unHoveredColor: [number, number, number] = [47, 47, 93]
 | 
						|
    expect(
 | 
						|
      await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
 | 
						|
    ).toBeLessThan(8)
 | 
						|
 | 
						|
    await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
 | 
						|
    await page.waitForTimeout(200)
 | 
						|
 | 
						|
    // color should not change for having been hovered
 | 
						|
    expect(
 | 
						|
      await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
 | 
						|
    ).toBeLessThan(8)
 | 
						|
 | 
						|
    await u.openAndClearDebugPanel()
 | 
						|
 | 
						|
    await u.codeLocator.fill(`sketch001 = startSketchOn(XY)
 | 
						|
    |> startProfile(at = [-10, -10])
 | 
						|
    |> line(end = [20, 0])
 | 
						|
    |> line(end = [0, 20])
 | 
						|
    |> xLine(length = -20)
 | 
						|
  `)
 | 
						|
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
 | 
						|
    const noPlanesColor: [number, number, number] = [30, 30, 30]
 | 
						|
    expect(
 | 
						|
      await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
 | 
						|
    ).toBeLessThan(3)
 | 
						|
 | 
						|
    await u.clearCommandLogs()
 | 
						|
    await u.removeCurrentCode()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
 | 
						|
    await expect
 | 
						|
      .poll(() => u.getGreatestPixDiff(XYPlanePoint, unHoveredColor), {
 | 
						|
        timeout: 5_000,
 | 
						|
      })
 | 
						|
      .toBeLessThan(8)
 | 
						|
 | 
						|
    // click start Sketch
 | 
						|
    await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
						|
    await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 50 })
 | 
						|
    const hoveredColor: [number, number, number] = [93, 93, 127]
 | 
						|
    // now that we're expecting the user to select a plan, it does respond to hover
 | 
						|
    await expect
 | 
						|
      .poll(() => u.getGreatestPixDiff(XYPlanePoint, hoveredColor))
 | 
						|
      .toBeLessThan(8)
 | 
						|
 | 
						|
    await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y)
 | 
						|
 | 
						|
    // wait for line button to have aria-pressed as proxy for sketch mode
 | 
						|
    await expect
 | 
						|
      .poll(async () => page.getByTestId('line').getAttribute('aria-pressed'), {
 | 
						|
        timeout: 10_000,
 | 
						|
      })
 | 
						|
      .toBe('true')
 | 
						|
    await page.waitForTimeout(200)
 | 
						|
 | 
						|
    await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y)
 | 
						|
    await page.waitForTimeout(200)
 | 
						|
    await page.mouse.click(XYPlanePoint.x + 50, XYPlanePoint.y + 50)
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [299.72, 230.82])
 | 
						|
  |> line(end = [86.12, -86.13])
 | 
						|
  `,
 | 
						|
      { shouldNormalise: true }
 | 
						|
    )
 | 
						|
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [299.72, 230.82])
 | 
						|
  |> line(end = [86.12, -86.13])
 | 
						|
  `
 | 
						|
      )
 | 
						|
    })
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
    await u.closeDebugPanel()
 | 
						|
 | 
						|
    // expect there to be no planes on load since there's something in the scene
 | 
						|
    expect(
 | 
						|
      await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
 | 
						|
    ).toBeLessThan(3)
 | 
						|
  })
 | 
						|
 | 
						|
  test('Can attempt to sketch on revolved face', async ({ page, homePage }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
						|
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `lugHeadLength = 0.25
 | 
						|
      lugDiameter = 0.5
 | 
						|
      lugLength = 2
 | 
						|
 | 
						|
      fn lug(origin, length, diameter, plane) {
 | 
						|
        lugSketch = startSketchOn(plane)
 | 
						|
          |> startProfile(at = [origin[0] + lugDiameter / 2, origin[1]])
 | 
						|
          |> angledLine(angle = 60, lengthY = lugHeadLength)
 | 
						|
          |> xLine(endAbsolute = 0 + .001)
 | 
						|
          |> yLine(endAbsolute = 0)
 | 
						|
          |> close()
 | 
						|
          |> revolve(axis = Y)
 | 
						|
 | 
						|
        return lugSketch
 | 
						|
      }
 | 
						|
 | 
						|
      lug(origin = [0, 0], length = 10, diameter = .5, plane = XY)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
    await u.closeDebugPanel()
 | 
						|
 | 
						|
    /***
 | 
						|
     * Test Plan
 | 
						|
     * Start the sketch mode
 | 
						|
     * Click the middle of the screen which should click the top face that is revolved
 | 
						|
     * Wait till you see the line tool be enabled
 | 
						|
     * Wait till you see the exit sketch enabled
 | 
						|
     *
 | 
						|
     * This is supposed to test that you are allowed to go into sketch mode to sketch on a revolved face
 | 
						|
     */
 | 
						|
 | 
						|
    await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
						|
 | 
						|
    await expect(async () => {
 | 
						|
      await page.mouse.click(600, 250)
 | 
						|
      await page.waitForTimeout(1000)
 | 
						|
      await expect(
 | 
						|
        page.getByRole('button', { name: 'Exit Sketch' })
 | 
						|
      ).toBeVisible()
 | 
						|
      await expect(
 | 
						|
        page.getByRole('button', { name: 'line Line', exact: true })
 | 
						|
      ).toHaveAttribute('aria-pressed', 'true')
 | 
						|
    }).toPass({ timeout: 40_000, intervals: [1_000] })
 | 
						|
  })
 | 
						|
 | 
						|
  test('sketch on face of a boolean works', async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    cmdBar,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
  }) => {
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = mm)
 | 
						|
 | 
						|
myVar = 50
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = circle(sketch001, center = [myVar, 43.9], radius = 41.05)
 | 
						|
extrude001 = extrude(profile001, length = 200)
 | 
						|
  |> translate(x = 3.14, y = 3.14, z = -50.154)
 | 
						|
sketch002 = startSketchOn(XY)
 | 
						|
profile002 = startProfile(sketch002, at = [72.2, -52.05])
 | 
						|
  |> angledLine(angle = 0, length = 181.26, tag = $rectangleSegmentA001)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 21.54)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $mySeg)
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
 | 
						|
  |> close()
 | 
						|
 | 
						|
extrude002 = extrude(profile002, length = 151)
 | 
						|
solid001 = subtract([extrude001], tools = [extrude002])
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    const [selectChamferFaceClk] = scene.makeMouseHelpers(705, 234)
 | 
						|
    const [circleCenterClk] = scene.makeMouseHelpers(700, 272)
 | 
						|
    const [circleRadiusClk] = scene.makeMouseHelpers(694, 264)
 | 
						|
 | 
						|
    await test.step('Setup', async () => {
 | 
						|
      await homePage.goToModelingScene()
 | 
						|
      await scene.settled(cmdBar)
 | 
						|
 | 
						|
      await scene.moveCameraTo(
 | 
						|
        { x: 180, y: -75, z: 116 },
 | 
						|
        { x: 67, y: -114, z: -15 }
 | 
						|
      )
 | 
						|
      await toolbar.waitForFeatureTreeToBeBuilt()
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('sketch on chamfer face that is part of a boolean', async () => {
 | 
						|
      await toolbar.startSketchPlaneSelection()
 | 
						|
      await selectChamferFaceClk()
 | 
						|
 | 
						|
      await expect
 | 
						|
        .poll(async () => {
 | 
						|
          const lineBtn = page.getByRole('button', { name: 'line Line' })
 | 
						|
          return lineBtn.getAttribute('aria-pressed')
 | 
						|
        })
 | 
						|
        .toBe('true')
 | 
						|
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        'startSketchOn(solid001, face = seg01)'
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('verify sketching still works', async () => {
 | 
						|
      await toolbar.circleBtn.click()
 | 
						|
      await expect
 | 
						|
        .poll(async () => {
 | 
						|
          const circleBtn = page.getByRole('button', { name: 'circle Circle' })
 | 
						|
          return circleBtn.getAttribute('aria-pressed')
 | 
						|
        })
 | 
						|
        .toBe('true')
 | 
						|
 | 
						|
      await circleCenterClk()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        'profile003 = circle(sketch003, center'
 | 
						|
      )
 | 
						|
 | 
						|
      await circleRadiusClk()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        'profile003 = circle(sketch003, center = [-25.77, 10.97], radius = 1.85)'
 | 
						|
      )
 | 
						|
    })
 | 
						|
  })
 | 
						|
 | 
						|
  test('Can sketch on face when user defined function was used in the sketch', async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
  }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
						|
 | 
						|
    // Checking for a regression that performs a sketch when a user defined function
 | 
						|
    // is declared at the top of the file and used in the sketch that is being drawn on.
 | 
						|
    // fn in2mm is declared at the top of the file and used rail which does a an extrusion with the function.
 | 
						|
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `fn in2mm(@inches) {
 | 
						|
    return inches * 25.4
 | 
						|
  }
 | 
						|
 | 
						|
  railTop = in2mm(.748)
 | 
						|
  railSide = in2mm(.024)
 | 
						|
  railBaseWidth = in2mm(.612)
 | 
						|
  railWideWidth = in2mm(.835)
 | 
						|
  railBaseLength = in2mm(.200)
 | 
						|
  railClampable = in2mm(.200)
 | 
						|
 | 
						|
  rail = startSketchOn(XZ)
 | 
						|
    |> startProfile(at = [-railTop / 2, railClampable + railBaseLength])
 | 
						|
    |> line(endAbsolute = [
 | 
						|
     railTop / 2,
 | 
						|
     railClampable + railBaseLength
 | 
						|
   ])
 | 
						|
    |> line(endAbsolute = [
 | 
						|
     railWideWidth / 2,
 | 
						|
     railClampable / 2 + railBaseLength
 | 
						|
   ], tag = $seg01)
 | 
						|
    |> line(endAbsolute = [railTop / 2, railBaseLength])
 | 
						|
    |> line(endAbsolute = [railBaseWidth / 2, railBaseLength])
 | 
						|
    |> line(endAbsolute = [railBaseWidth / 2, 0])
 | 
						|
    |> line(endAbsolute = [-railBaseWidth / 2, 0])
 | 
						|
    |> line(endAbsolute = [-railBaseWidth / 2, railBaseLength])
 | 
						|
    |> line(endAbsolute = [-railTop / 2, railBaseLength])
 | 
						|
    |> line(endAbsolute = [
 | 
						|
     -railWideWidth / 2,
 | 
						|
     railClampable / 2 + railBaseLength
 | 
						|
   ])
 | 
						|
    |> line(endAbsolute = [
 | 
						|
     -railTop / 2,
 | 
						|
     railClampable + railBaseLength
 | 
						|
   ])
 | 
						|
    |> close()
 | 
						|
    |> extrude(length = in2mm(2))`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    const center = { x: 600, y: 250 }
 | 
						|
    const rectangleSize = 20
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
 | 
						|
    // Start a sketch
 | 
						|
    await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
						|
 | 
						|
    // Click the top face of this rail
 | 
						|
    await page.mouse.click(center.x, center.y)
 | 
						|
    await page.waitForTimeout(1000)
 | 
						|
 | 
						|
    // Draw a rectangle
 | 
						|
    // top left
 | 
						|
    await page.mouse.click(center.x - rectangleSize, center.y - rectangleSize)
 | 
						|
    await page.waitForTimeout(250)
 | 
						|
    // top right
 | 
						|
    await page.mouse.click(center.x + rectangleSize, center.y - rectangleSize)
 | 
						|
    await page.waitForTimeout(250)
 | 
						|
 | 
						|
    // bottom right
 | 
						|
    await page.mouse.click(center.x + rectangleSize, center.y + rectangleSize)
 | 
						|
    await page.waitForTimeout(250)
 | 
						|
 | 
						|
    // bottom left
 | 
						|
    await page.mouse.click(center.x - rectangleSize, center.y + rectangleSize)
 | 
						|
    await page.waitForTimeout(250)
 | 
						|
 | 
						|
    // top left
 | 
						|
    await page.mouse.click(center.x - rectangleSize, center.y - rectangleSize)
 | 
						|
    await page.waitForTimeout(250)
 | 
						|
 | 
						|
    // exit sketch
 | 
						|
    await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
						|
 | 
						|
    // Check execution is done
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
    await u.closeDebugPanel()
 | 
						|
  })
 | 
						|
 | 
						|
  test('Can edit a tangentialArc defined by angle and radius', async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
    editor,
 | 
						|
    toolbar,
 | 
						|
    scene,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const viewportSize = { width: 1500, height: 750 }
 | 
						|
    await page.setBodyDimensions(viewportSize)
 | 
						|
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit=in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
  |> startProfile(at = [-10, -10])
 | 
						|
  |> line(end = [20.0, 10.0])
 | 
						|
  |> tangentialArc(angle = 60deg, radius=10.0)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await toolbar.waitForFeatureTreeToBeBuilt()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
 | 
						|
 | 
						|
    await page.waitForTimeout(1000)
 | 
						|
 | 
						|
    await page.mouse.move(1200, 139)
 | 
						|
    await page.mouse.down()
 | 
						|
    await page.mouse.move(870, 250)
 | 
						|
 | 
						|
    await page.waitForTimeout(200)
 | 
						|
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `tangentialArc(angle = 234.01deg, radius = 4.08)`,
 | 
						|
      { shouldNormalise: true }
 | 
						|
    )
 | 
						|
  })
 | 
						|
 | 
						|
  test('Can delete a single segment line with keyboard', async ({
 | 
						|
    page,
 | 
						|
    scene,
 | 
						|
    homePage,
 | 
						|
    cmdBar,
 | 
						|
    editor,
 | 
						|
  }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
 | 
						|
    const viewportSize = { width: 1100, height: 750 }
 | 
						|
    await page.setBodyDimensions(viewportSize)
 | 
						|
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = mm)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [0, 0])
 | 
						|
  |> xLine(length = 25.0)
 | 
						|
  |> yLine(length = 5.0)
 | 
						|
  |> line(end = [-22.0, 12.0])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    // Enter sketch mode
 | 
						|
    await page.mouse.dblclick(654, 450)
 | 
						|
    await page.waitForTimeout(1000)
 | 
						|
 | 
						|
    // Select the third line
 | 
						|
    await page.mouse.click(918, 90)
 | 
						|
    await page.waitForTimeout(1000)
 | 
						|
 | 
						|
    // Delete with backspace
 | 
						|
    await page.keyboard.press('Delete')
 | 
						|
 | 
						|
    // Wait for engine re-execution to complete
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
 | 
						|
 | 
						|
    // Validate the editor code no longer contains the deleted line
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [0, 0])
 | 
						|
  |> xLine(length = 25.0)
 | 
						|
  |> yLine(length = 5.0)
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
`,
 | 
						|
      { shouldNormalise: true }
 | 
						|
    )
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
test.describe(`Sketching with offset planes`, () => {
 | 
						|
  test(`Can select an offset plane to sketch on`, async ({
 | 
						|
    context,
 | 
						|
    page,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    homePage,
 | 
						|
  }) => {
 | 
						|
    // We seed the scene with a single offset plane
 | 
						|
    await context.addInitScript(() => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
offsetPlane001 = offsetPlane(XY, offset = 10)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
 | 
						|
    const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200)
 | 
						|
 | 
						|
    await test.step(`Start sketching on the offset plane`, async () => {
 | 
						|
      await toolbar.startSketchPlaneSelection()
 | 
						|
 | 
						|
      await test.step(`Hovering should highlight code`, async () => {
 | 
						|
        await planeHover()
 | 
						|
        await editor.expectState({
 | 
						|
          activeLines: [`@settings(defaultLengthUnit = in)`],
 | 
						|
          diagnostics: [],
 | 
						|
          highlightedCode: 'offsetPlane(XY, offset = 10)',
 | 
						|
        })
 | 
						|
      })
 | 
						|
 | 
						|
      await test.step(`Clicking should select the plane and enter sketch mode`, async () => {
 | 
						|
        await planeClick()
 | 
						|
        // Have to wait for engine-side animation to finish
 | 
						|
        await page.waitForTimeout(600)
 | 
						|
        await expect(toolbar.lineBtn).toBeEnabled()
 | 
						|
        await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
 | 
						|
        await editor.expectState({
 | 
						|
          activeLines: [`@settings(defaultLengthUnit = in)`],
 | 
						|
          diagnostics: [],
 | 
						|
          highlightedCode: '',
 | 
						|
        })
 | 
						|
      })
 | 
						|
    })
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
test.describe('multi-profile sketching', () => {
 | 
						|
  test(`test it removes half-finished expressions when changing tools in sketch mode`, async ({
 | 
						|
    context,
 | 
						|
    page,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    homePage,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    // We seed the scene with a single offset plane
 | 
						|
    await context.addInitScript(() => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `yo = 5
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [121.52, 168.25])
 | 
						|
  |> line(end = [115.04, 113.61])
 | 
						|
  |> line(end = [130.87, -97.79])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile002 = startProfile(sketch001, at = [117.2, 56.08])
 | 
						|
  |> line(end = [166.82, 25.89])
 | 
						|
  |> yLine(length = -107.86)
 | 
						|
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    const [continueProfile2Clk] = scene.makeMouseHelpers(954, 282)
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
 | 
						|
    await page.waitForTimeout(600)
 | 
						|
 | 
						|
    const [circlePoint1] = scene.makeMouseHelpers(700, 200)
 | 
						|
 | 
						|
    await test.step('equip circle tool and click first point', async () => {
 | 
						|
      // await page.waitForTimeout(100)
 | 
						|
      await expect
 | 
						|
        .poll(async () => {
 | 
						|
          await toolbar.circleBtn.click()
 | 
						|
          return toolbar.circleBtn.getAttribute('aria-pressed')
 | 
						|
        })
 | 
						|
        .toBe('true')
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await circlePoint1()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        'profile003 = circle(sketch001, center = ['
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('equip line tool and verify circle code is removed', async () => {
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await editor.expectEditor.not.toContain('profile003 = circle(')
 | 
						|
    })
 | 
						|
 | 
						|
    const [circle3Point1] = scene.makeMouseHelpers(650, 200)
 | 
						|
    const [circle3Point2] = scene.makeMouseHelpers(750, 200)
 | 
						|
    // const [circle3Point3] = scene.makeMouseHelpers(700, 150)
 | 
						|
 | 
						|
    await test.step('equip three point circle tool and click first two points', async () => {
 | 
						|
      await toolbar.selectCircleThreePoint()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await circle3Point1()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await circle3Point2()
 | 
						|
      await editor.expectEditor.toContain('profile003 = circleThreePoint(')
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('equip line tool and verify three-point circle code is removed', async () => {
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await editor.expectEditor.not.toContain('profile003 = circleThreePoint(')
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('equip three-point-arc tool and click first two points', async () => {
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      await toolbar.selectThreePointArc()
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      await circle3Point1()
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      await circle3Point2()
 | 
						|
      await editor.expectEditor.toContain('arc(')
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('equip line tool and verify three-point-arc code is removed after second click', async () => {
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await editor.expectEditor.not.toContain('arc(')
 | 
						|
    })
 | 
						|
 | 
						|
    const [cornerRectPoint1] = scene.makeMouseHelpers(600, 300)
 | 
						|
 | 
						|
    await test.step('equip corner rectangle tool and click first point', async () => {
 | 
						|
      await toolbar.rectangleBtn.click()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await cornerRectPoint1()
 | 
						|
      await editor.expectEditor.toContain('profile004 = startProfile(')
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('equip line tool and verify corner rectangle code is removed', async () => {
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await editor.expectEditor.not.toContain('profile004 = startProfile(')
 | 
						|
    })
 | 
						|
 | 
						|
    const [centerRectPoint1] = scene.makeMouseHelpers(700, 300)
 | 
						|
 | 
						|
    await test.step('equip center rectangle tool and click first point', async () => {
 | 
						|
      await toolbar.selectCenterRectangle()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await centerRectPoint1()
 | 
						|
      await editor.expectEditor.toContain('profile004 = startProfile(')
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('equip line tool and verify center rectangle code is removed', async () => {
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await editor.expectEditor.not.toContain('profile004 = startProfile(')
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('continue profile002 with the three point arc tool, and then switch back to the line tool to verify it only removes the last expression in the pipe', async () => {
 | 
						|
      await toolbar.selectThreePointArc()
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      await continueProfile2Clk()
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      await circle3Point1()
 | 
						|
      await editor.expectEditor.toContain('arc(')
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await editor.expectEditor.not.toContain('arc(')
 | 
						|
      await editor.expectEditor.toContain('profile002')
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test(`snapToProfile start only works for current profile`, async ({
 | 
						|
    context,
 | 
						|
    page,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    homePage,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    // We seed the scene with a single offset plane
 | 
						|
    await context.addInitScript(() => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile002 = startProfile(sketch001, at = [40.68, 87.67])
 | 
						|
  |> xLine(length = 239.17)
 | 
						|
profile003 = startProfile(sketch001, at = [206.63, -56.73])
 | 
						|
  |> xLine(length = -156.32)
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const [onSegmentClick] = scene.makeMouseHelpers(604, 349)
 | 
						|
    const [endOfLowerSegClick, endOfLowerSegMove] = scene.makeMouseHelpers(
 | 
						|
      697,
 | 
						|
      360
 | 
						|
    )
 | 
						|
    const [profileStartOfHigherSegClick, profileStartOfHigherSegMove] =
 | 
						|
      scene.makeMouseHelpers(677, 78)
 | 
						|
    const tanArcLocation = { x: 624, y: 340 } as const
 | 
						|
 | 
						|
    await test.step('enter sketch mode', async () => {
 | 
						|
      await onSegmentClick({ shouldDbClick: true })
 | 
						|
      await page.waitForTimeout(600)
 | 
						|
    })
 | 
						|
 | 
						|
    const codeFromTangentialArc = `  |> tangentialArc(end = [-10.82, 144.95])`
 | 
						|
    await test.step('check that tangential tool does not snap to other profile starts', async () => {
 | 
						|
      await toolbar.selectTangentialArc()
 | 
						|
      await page.waitForTimeout(1000)
 | 
						|
      await endOfLowerSegMove()
 | 
						|
      await page.waitForTimeout(1000)
 | 
						|
      await endOfLowerSegClick()
 | 
						|
      await page.waitForTimeout(1000)
 | 
						|
      await profileStartOfHigherSegClick()
 | 
						|
      await page.waitForTimeout(1000)
 | 
						|
      await editor.expectEditor.toContain(codeFromTangentialArc)
 | 
						|
      await editor.expectEditor.not.toContain(
 | 
						|
        `[profileStartX(%), profileStartY(%)]`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('remove tangential arc code to reset', async () => {
 | 
						|
      await scene.expectPixelColor(TEST_COLORS.WHITE, tanArcLocation, 15)
 | 
						|
      await editor.replaceCode(codeFromTangentialArc, '')
 | 
						|
      // check pixel is now gray at tanArcLocation to verify code has executed
 | 
						|
      await scene.expectPixelColor([26, 26, 26], tanArcLocation, 15)
 | 
						|
      await editor.expectEditor.not.toContain(
 | 
						|
        `tangentialArc(end = [-10.82, 144.95])`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('check that tangential tool does snap to current profile start', async () => {
 | 
						|
      await expect
 | 
						|
        .poll(async () => {
 | 
						|
          await toolbar.lineBtn.click()
 | 
						|
          return toolbar.lineBtn.getAttribute('aria-pressed')
 | 
						|
        })
 | 
						|
        .toBe('true')
 | 
						|
      await profileStartOfHigherSegMove()
 | 
						|
      await endOfLowerSegMove()
 | 
						|
      await endOfLowerSegClick()
 | 
						|
      await profileStartOfHigherSegClick()
 | 
						|
      await editor.expectEditor.toContain('line(end = [-10.82, 144.95])')
 | 
						|
      await editor.expectEditor.not.toContain(
 | 
						|
        `[profileStartX(%), profileStartY(%)]`
 | 
						|
      )
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('can enter sketch mode for sketch with no profiles', async ({
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    cmdBar,
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
  }) => {
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `sketch001 = startSketchOn(XY)
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    // open feature tree and double click the first sketch
 | 
						|
    await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
 | 
						|
    await page.waitForTimeout(600)
 | 
						|
 | 
						|
    // click in the scene twice to add a segment
 | 
						|
    const [startProfile1] = scene.makeMouseHelpers(658, 140)
 | 
						|
    const [segment1Clk] = scene.makeMouseHelpers(701, 200)
 | 
						|
 | 
						|
    // wait for line to be aria pressed
 | 
						|
    await expect
 | 
						|
      .poll(async () => toolbar.lineBtn.getAttribute('aria-pressed'))
 | 
						|
      .toBe('true')
 | 
						|
 | 
						|
    await startProfile1()
 | 
						|
    await editor.expectEditor.toContain(`profile001 = startProfile`)
 | 
						|
    await segment1Clk()
 | 
						|
    await editor.expectEditor.toContain(`|> line(end`)
 | 
						|
  })
 | 
						|
  test('can delete all profiles in sketch mode and user can still equip a tool and draw something', async ({
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
  }) => {
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const [selectXZPlane] = scene.makeMouseHelpers(650, 150)
 | 
						|
 | 
						|
    await toolbar.startSketchPlaneSelection()
 | 
						|
    await selectXZPlane()
 | 
						|
    // timeout wait for engine animation is unavoidable
 | 
						|
    await page.waitForTimeout(600)
 | 
						|
    await editor.expectEditor.toContain(`sketch001 = startSketchOn(XZ)`)
 | 
						|
 | 
						|
    const [startProfile1] = scene.makeMouseHelpers(568, 70)
 | 
						|
    const [segment1Clk] = scene.makeMouseHelpers(701, 78)
 | 
						|
    const [segment2Clk] = scene.makeMouseHelpers(745, 189)
 | 
						|
 | 
						|
    await test.step('add two segments', async () => {
 | 
						|
      await startProfile1()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile001 = startProfile(sketch001, at = [4.61, 12.21])`
 | 
						|
      )
 | 
						|
      await segment1Clk()
 | 
						|
      await editor.expectEditor.toContain(`|> line(end`)
 | 
						|
      await segment2Clk()
 | 
						|
      await editor.expectEditor.toContain(`|> line(end = [2.98, -7.52])`)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('delete all profiles', async () => {
 | 
						|
      await editor.replaceCode('', 'sketch001 = startSketchOn(XZ)\n')
 | 
						|
      await page.waitForTimeout(600) // wait for deferred execution
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('equip circle and draw it', async () => {
 | 
						|
      await toolbar.circleBtn.click()
 | 
						|
      await page.mouse.click(700, 200)
 | 
						|
      await page.mouse.click(750, 200)
 | 
						|
      await editor.expectEditor.toContain('circle(sketch001, center = [')
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('Can add multiple profiles to a sketch (all tool types)', async ({
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
  }) => {
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const [selectXZPlane] = scene.makeMouseHelpers(650, 150)
 | 
						|
 | 
						|
    const [startProfile1] = scene.makeMouseHelpers(568, 70)
 | 
						|
    const [endLineStartTanArc] = scene.makeMouseHelpers(701, 78)
 | 
						|
    const [endArcStartLine] = scene.makeMouseHelpers(745, 189)
 | 
						|
 | 
						|
    const [startProfile2] = scene.makeMouseHelpers(782, 80)
 | 
						|
    const [profile2Point2] = scene.makeMouseHelpers(921, 90)
 | 
						|
    const [profile2Point3] = scene.makeMouseHelpers(953, 178)
 | 
						|
 | 
						|
    const [circle1Center] = scene.makeMouseHelpers(842, 147)
 | 
						|
    const [circle1Radius] = scene.makeMouseHelpers(870, 171)
 | 
						|
 | 
						|
    const [circle2Center] = scene.makeMouseHelpers(850, 222)
 | 
						|
    const [circle2Radius] = scene.makeMouseHelpers(843, 230)
 | 
						|
 | 
						|
    const [crnRect1point1] = scene.makeMouseHelpers(583, 205)
 | 
						|
    const [crnRect1point2] = scene.makeMouseHelpers(618, 320)
 | 
						|
 | 
						|
    const [crnRect2point1] = scene.makeMouseHelpers(663, 215)
 | 
						|
    const [crnRect2point2] = scene.makeMouseHelpers(744, 276)
 | 
						|
 | 
						|
    const [cntrRect1point1] = scene.makeMouseHelpers(624, 387)
 | 
						|
    const [cntrRect1point2] = scene.makeMouseHelpers(676, 355)
 | 
						|
 | 
						|
    const [cntrRect2point1] = scene.makeMouseHelpers(785, 332)
 | 
						|
    const [cntrRect2point2] = scene.makeMouseHelpers(808, 286)
 | 
						|
 | 
						|
    const [circle3Point1p1, circle3Point1p1Move] = scene.makeMouseHelpers(
 | 
						|
      630,
 | 
						|
      465
 | 
						|
    )
 | 
						|
    const [circle3Point1p2, circle3Point1p2Move] = scene.makeMouseHelpers(
 | 
						|
      673,
 | 
						|
      340
 | 
						|
    )
 | 
						|
    const [circle3Point1p3, circle3Point1p3Move] = scene.makeMouseHelpers(
 | 
						|
      734,
 | 
						|
      414
 | 
						|
    )
 | 
						|
 | 
						|
    const [circle3Point2p1, circle3Point2p1Move] = scene.makeMouseHelpers(
 | 
						|
      876,
 | 
						|
      351
 | 
						|
    )
 | 
						|
    const [circle3Point2p2, circle3Point2p2Move] = scene.makeMouseHelpers(
 | 
						|
      875,
 | 
						|
      279
 | 
						|
    )
 | 
						|
    const [circle3Point2p3, circle3Point2p3Move] = scene.makeMouseHelpers(
 | 
						|
      834,
 | 
						|
      306
 | 
						|
    )
 | 
						|
 | 
						|
    await toolbar.startSketchPlaneSelection()
 | 
						|
    await selectXZPlane()
 | 
						|
    // timeout wait for engine animation is unavoidable
 | 
						|
    await page.waitForTimeout(600)
 | 
						|
    await editor.expectEditor.toContain(`sketch001 = startSketchOn(XZ)`)
 | 
						|
    await test.step('Create a close profile stopping mid profile to equip the tangential arc, then three-point arc, and then back to the line tool', async () => {
 | 
						|
      await startProfile1()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile001 = startProfile(sketch001, at = [4.61, 12.21])`
 | 
						|
      )
 | 
						|
 | 
						|
      await endLineStartTanArc()
 | 
						|
      await editor.expectEditor.toContain(`|> line(end = [9.02, -0.55])`)
 | 
						|
      await toolbar.selectTangentialArc()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await page.mouse.click(745, 359)
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await endLineStartTanArc({ delay: 544 })
 | 
						|
 | 
						|
      await endArcStartLine()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `|> tangentialArc(end = [2.98, -7.52])`
 | 
						|
      )
 | 
						|
 | 
						|
      // Add a three-point arc segment
 | 
						|
      await toolbar.selectThreePointArc()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
 | 
						|
      // select end of profile again
 | 
						|
      await endLineStartTanArc()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
 | 
						|
      // Define points for the three-point arc
 | 
						|
      const [threePointInterior, threePointInteriorMove] =
 | 
						|
        scene.makeMouseHelpers(600, 200)
 | 
						|
      const [threePointEnd, threePointEndMove] = scene.makeMouseHelpers(
 | 
						|
        590,
 | 
						|
        270
 | 
						|
      )
 | 
						|
 | 
						|
      // Create the three-point arc
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await threePointInteriorMove()
 | 
						|
      await threePointInterior()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await threePointEndMove()
 | 
						|
      await threePointEnd()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
 | 
						|
      // Verify the three-point arc was created correctly
 | 
						|
      await editor.expectEditor.toContain(`arc(`)
 | 
						|
      await editor.expectEditor.toContain(`interiorAbsolute`)
 | 
						|
      await editor.expectEditor.toContain(`endAbsolute`)
 | 
						|
 | 
						|
      // Switch back to line tool to continue
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
 | 
						|
      // Continue with the original line segment
 | 
						|
      await threePointEnd()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
 | 
						|
      await page.mouse.click(572, 110)
 | 
						|
      await editor.expectEditor.toContain(`|> line(end = [-1.22, 10.85])`)
 | 
						|
      await startProfile1()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()`,
 | 
						|
        { shouldNormalise: true }
 | 
						|
      )
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('Without unequipping from the last step, make another profile, and one that is not closed', async () => {
 | 
						|
      await startProfile2()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile002 = startProfile(sketch001, at = [19.12, 11.53])`
 | 
						|
      )
 | 
						|
      await profile2Point2()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(`|> line(end = [9.43, -0.68])`)
 | 
						|
      await profile2Point3()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(`|> line(end = [2.17, -5.97])`)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('create two circles in a row without unequip', async () => {
 | 
						|
      await toolbar.circleBtn.click()
 | 
						|
 | 
						|
      await circle1Center()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await circle1Radius({ delay: 500 })
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile003 = circle(sketch001, center = [23.19, 6.98], radius = 2.5)`
 | 
						|
      )
 | 
						|
 | 
						|
      await test.step('hover in empty space to wait for overlays to get out of the way', async () => {
 | 
						|
        await page.mouse.move(951, 223)
 | 
						|
        await page.waitForTimeout(1000)
 | 
						|
      })
 | 
						|
 | 
						|
      await circle2Center()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await circle2Radius()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile004 = circle(sketch001, center = [23.74, 1.9], radius = 0.72)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
    await test.step('create two corner rectangles in a row without unequip', async () => {
 | 
						|
      await expect
 | 
						|
        .poll(async () => {
 | 
						|
          await toolbar.rectangleBtn.click()
 | 
						|
          return toolbar.rectangleBtn.getAttribute('aria-pressed')
 | 
						|
        })
 | 
						|
        .toBe('true')
 | 
						|
 | 
						|
      await crnRect1point1()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile005 = startProfile(sketch001, at = [5.63, 3.05])`
 | 
						|
      )
 | 
						|
      await crnRect1point2()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `|> angledLine(angle = 0, length = 2.37, tag = $rectangleSegmentA001)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 7.8)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()`.replaceAll('\n', '')
 | 
						|
      )
 | 
						|
 | 
						|
      await crnRect2point1()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile006 = startProfile(sketch001, at = [11.05, 2.37])`
 | 
						|
      )
 | 
						|
      await crnRect2point2()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `|> angledLine(angle = 0, length = 5.49, tag = $rectangleSegmentA002)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA002) - 90, length = 4.14)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()`.replaceAll('\n', '')
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('create two center rectangles in a row without unequip', async () => {
 | 
						|
      await toolbar.selectCenterRectangle()
 | 
						|
 | 
						|
      await cntrRect1point1()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile007 = startProfile(sketch001, at = [8.41, -9.29])`
 | 
						|
      )
 | 
						|
      await cntrRect1point2()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `|> angledLine(angle = 0, length = 7.06, tag = $rectangleSegmentA003)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA003) + 90, length = 4.34)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA003), length = -segLen(rectangleSegmentA003))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()`.replaceAll('\n', '')
 | 
						|
      )
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
 | 
						|
      await cntrRect2point1()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile008 = startProfile(sketch001, at = [19.33, -5.56])`
 | 
						|
      )
 | 
						|
      await cntrRect2point2()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `|> angledLine(angle = 0, length = 3.12, tag = $rectangleSegmentA004)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA004) + 90, length = 6.24)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA004), length = -segLen(rectangleSegmentA004))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()`.replaceAll('\n', '')
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('create two circle-three-points in a row without an unequip', async () => {
 | 
						|
      await toolbar.selectCircleThreePoint()
 | 
						|
 | 
						|
      await circle3Point1p1Move()
 | 
						|
      await circle3Point1p1()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await circle3Point1p2Move()
 | 
						|
      await circle3Point1p2()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile009 = circleThreePoint(
 | 
						|
  sketch001,
 | 
						|
  p1 = [8.82, -14.58],
 | 
						|
  p2 = [11.73, -6.1],
 | 
						|
  p3 = [11.83, -6],
 | 
						|
)`,
 | 
						|
        { shouldNormalise: true }
 | 
						|
      )
 | 
						|
 | 
						|
      await circle3Point1p3Move()
 | 
						|
      await circle3Point1p3()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile009 = circleThreePoint(
 | 
						|
  sketch001,
 | 
						|
  p1 = [8.82, -14.58],
 | 
						|
  p2 = [11.73, -6.1],
 | 
						|
  p3 = [15.87, -11.12],
 | 
						|
)`,
 | 
						|
        { shouldNormalise: true }
 | 
						|
      )
 | 
						|
 | 
						|
      await circle3Point2p1Move()
 | 
						|
      await circle3Point2p1()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await circle3Point2p2Move()
 | 
						|
      await circle3Point2p2()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile010 = circleThreePoint(
 | 
						|
  sketch001,
 | 
						|
  p1 = [25.5, -6.85],
 | 
						|
  p2 = [25.43, -1.97],
 | 
						|
  p3 = [25.53, -1.87],
 | 
						|
)`,
 | 
						|
        { shouldNormalise: true }
 | 
						|
      )
 | 
						|
 | 
						|
      await circle3Point2p3Move()
 | 
						|
      await circle3Point2p3()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile010 = circleThreePoint(
 | 
						|
  sketch001,
 | 
						|
  p1 = [25.5, -6.85],
 | 
						|
  p2 = [25.43, -1.97],
 | 
						|
  p3 = [22.65, -3.8],
 | 
						|
)`,
 | 
						|
        { shouldNormalise: true }
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('create three-point arcs in a row without an unequip', async () => {
 | 
						|
      // Define points for the first three-point arc
 | 
						|
      const [arc1Point1, arc1Point1Move] = scene.makeMouseHelpers(700, 397)
 | 
						|
      const [arc1Point2, arc1Point2Move] = scene.makeMouseHelpers(724, 346)
 | 
						|
      const [arc1Point3, arc1Point3Move] = scene.makeMouseHelpers(785, 415)
 | 
						|
 | 
						|
      // Define points for the second three-point arc
 | 
						|
      const [arc2Point1, arc2Point1Move] = scene.makeMouseHelpers(792, 225)
 | 
						|
      const [arc2Point2, arc2Point2Move] = scene.makeMouseHelpers(820, 207)
 | 
						|
      const [arc2Point3, arc2Point3Move] = scene.makeMouseHelpers(905, 229)
 | 
						|
 | 
						|
      // Select the three-point arc tool
 | 
						|
      await toolbar.selectThreePointArc()
 | 
						|
 | 
						|
      // Create the first three-point arc
 | 
						|
      await arc1Point1Move()
 | 
						|
      await arc1Point1()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await arc1Point2Move()
 | 
						|
      await arc1Point2()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await arc1Point3Move()
 | 
						|
      await arc1Point3()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
 | 
						|
      // Verify the first three-point arc was created correctly
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile011 = startProfile(sketch001, at = [13.56, -9.97])
 | 
						|
  |> arc(interiorAbsolute = [15.19, -6.51], endAbsolute = [19.33, -11.19])`,
 | 
						|
        { shouldNormalise: true }
 | 
						|
      )
 | 
						|
 | 
						|
      // Create the second three-point arc
 | 
						|
      await arc2Point1Move()
 | 
						|
      await arc2Point1()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await arc2Point2Move()
 | 
						|
      await arc2Point2()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await arc2Point3Move()
 | 
						|
      await arc2Point3()
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
 | 
						|
      // Verify the second three-point arc was created correctly
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `  |> arc(interiorAbsolute = [19.8, 1.7], endAbsolute = [21.7, 2.92])
 | 
						|
  |> arc(interiorAbsolute = [27.47, 1.42], endAbsolute = [27.57, 1.52])`,
 | 
						|
        { shouldNormalise: true }
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('double check that three-point arc can be unequipped', async () => {
 | 
						|
      // this was tested implicitly for other tools, but not for three-point arc since it's last
 | 
						|
      await page.waitForTimeout(300)
 | 
						|
      await expect
 | 
						|
        .poll(async () => {
 | 
						|
          await toolbar.lineBtn.click()
 | 
						|
          return toolbar.lineBtn.getAttribute('aria-pressed')
 | 
						|
        })
 | 
						|
        .toBe('true')
 | 
						|
    })
 | 
						|
  })
 | 
						|
 | 
						|
  test('Can edit a sketch with multiple profiles, dragging segments to edit them, and adding one new profile', async ({
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    page,
 | 
						|
  }) => {
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [6.24, 4.54])
 | 
						|
  |> line(end = [-0.41, 6.99])
 | 
						|
  |> line(end = [8.61, 0.74])
 | 
						|
  |> line(end = [10.99, -5.22])
 | 
						|
profile002 = startProfile(sketch001, at = [11.19, 5.02])
 | 
						|
  |> angledLine(angle = 0, length = 10.78, tag = $rectangleSegmentA001)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 4.14)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
 | 
						|
profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07], p3 = [18.75, -4.41])
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    // The text to prompt popover gets in the way of pointOnSegment click otherwise
 | 
						|
    const moveToClearToolBarPopover = scene.makeMouseHelpers(590, 500)[1]
 | 
						|
 | 
						|
    const [pointOnSegment] = scene.makeMouseHelpers(590, 141)
 | 
						|
    const [profileEnd] = scene.makeMouseHelpers(970, 105)
 | 
						|
    const profileEndMv = scene.makeMouseHelpers(951, 101)[1]
 | 
						|
    const [newProfileEnd] = scene.makeMouseHelpers(764, 104)
 | 
						|
    const dragSegmentTo = scene.makeMouseHelpers(850, 104)[1]
 | 
						|
 | 
						|
    const rectHandle = scene.makeMouseHelpers(901, 150)[1]
 | 
						|
    const rectDragTo = scene.makeMouseHelpers(901, 180)[1]
 | 
						|
 | 
						|
    const circleEdge = scene.makeMouseHelpers(691, 331)[1]
 | 
						|
    const dragCircleTo = scene.makeMouseHelpers(720, 331)[1]
 | 
						|
 | 
						|
    const [rectStart] = scene.makeMouseHelpers(794, 322)
 | 
						|
    const [rectEnd] = scene.makeMouseHelpers(757, 395)
 | 
						|
 | 
						|
    const [circ3PStart] = scene.makeMouseHelpers(854, 332)
 | 
						|
    const [circ3PEnd] = scene.makeMouseHelpers(870, 275)
 | 
						|
 | 
						|
    await test.step('enter sketch and setup', async () => {
 | 
						|
      await moveToClearToolBarPopover()
 | 
						|
      await page.waitForTimeout(1000)
 | 
						|
      await pointOnSegment({ shouldDbClick: true })
 | 
						|
      await page.waitForTimeout(2000)
 | 
						|
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('extend existing profile', async () => {
 | 
						|
      await profileEnd()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await newProfileEnd()
 | 
						|
      await editor.expectEditor.toContain(`|> line(end = [-11.35, 0.73])`)
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('edit existing profile', async () => {
 | 
						|
      await profileEndMv()
 | 
						|
      await page.mouse.down()
 | 
						|
      await dragSegmentTo()
 | 
						|
      await page.mouse.up()
 | 
						|
      await editor.expectEditor.toContain(`line(end = [4.22, -4.49])`)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('edit existing rect', async () => {
 | 
						|
      await rectHandle()
 | 
						|
      await page.mouse.down()
 | 
						|
      await rectDragTo()
 | 
						|
      await page.mouse.up()
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `angledLine(angle = -7, length = 10.27, tag = $rectangleSegmentA001)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('edit existing circle', async () => {
 | 
						|
      await circleEdge()
 | 
						|
      await page.mouse.down()
 | 
						|
      await dragCircleTo()
 | 
						|
      await page.mouse.up()
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile003 = circle(sketch001, center = [6.92, -4.2], radius = 4.81)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('edit existing circle three point', async () => {
 | 
						|
      await circ3PStart()
 | 
						|
      await page.mouse.down()
 | 
						|
      await circ3PEnd()
 | 
						|
      await page.mouse.up()
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile004 = circleThreePoint(
 | 
						|
  sketch001,
 | 
						|
  p1 = [13.44, -6.8],
 | 
						|
  p2 = [13.39, -2.07],
 | 
						|
  p3 = [19.73, -1.33],
 | 
						|
)`,
 | 
						|
        { shouldNormalise: true }
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('add new profile', async () => {
 | 
						|
      await toolbar.rectangleBtn.click()
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      await rectStart()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile005 = startProfile(sketch001, at = [15.68, -3.84])`
 | 
						|
      )
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await rectEnd()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `|> angledLine(angle = 180, length = 1.97, tag = $rectangleSegmentA002)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA002) + 90, length = 3.89)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()`.replaceAll('\n', '')
 | 
						|
      )
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('Can delete a profile in the editor while is sketch mode, and sketch mode does not break, can ctrl+z to undo after constraint with variable was added', async ({
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    cmdBar,
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
  }) => {
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [6.24, 4.54])
 | 
						|
  |> line(end = [-0.41, 6.99])
 | 
						|
  |> line(end = [8.61, 0.74])
 | 
						|
  |> line(end = [10.99, -5.22])
 | 
						|
profile002 = startProfile(sketch001, at = [11.19, 5.02])
 | 
						|
  |> angledLine(angle = 0, length = 10.78, tag = $rectangleSegmentA001)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 4.14)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    // The text to prompt popover gets in the way of pointOnSegment click otherwise
 | 
						|
    const moveToClearToolBarPopover = scene.makeMouseHelpers(590, 500)[1]
 | 
						|
 | 
						|
    const [pointOnSegment] = scene.makeMouseHelpers(590, 141)
 | 
						|
    const [segment1Click] = scene.makeMouseHelpers(616, 131)
 | 
						|
    const sketchIsDrawnProperly = async () => {
 | 
						|
      await test.step('check the sketch is still drawn properly', async () => {
 | 
						|
        await page.waitForTimeout(200)
 | 
						|
        await scene.expectPixelColor([255, 255, 255], { x: 617, y: 163 }, 15)
 | 
						|
        await scene.expectPixelColor([255, 255, 255], { x: 629, y: 331 }, 15)
 | 
						|
      })
 | 
						|
    }
 | 
						|
 | 
						|
    await test.step('enter sketch and setup', async () => {
 | 
						|
      await moveToClearToolBarPopover()
 | 
						|
      await pointOnSegment({ shouldDbClick: true })
 | 
						|
      await page.waitForTimeout(600)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('select and delete code for a profile', async () => {})
 | 
						|
    await page.getByText('close()').click()
 | 
						|
    await page.keyboard.down('Shift')
 | 
						|
    for (let i = 0; i < 11; i++) {
 | 
						|
      await page.keyboard.press('ArrowUp')
 | 
						|
    }
 | 
						|
    await page.keyboard.press('Home')
 | 
						|
    await page.keyboard.up('Shift')
 | 
						|
    await page.keyboard.press('Backspace')
 | 
						|
 | 
						|
    await sketchIsDrawnProperly()
 | 
						|
 | 
						|
    await test.step('add random new var between profiles', async () => {
 | 
						|
      await page.keyboard.type('myVar = 5')
 | 
						|
      await page.keyboard.press('Enter')
 | 
						|
      // If this timeout isn't long enough, the test breaks.
 | 
						|
      // TODO: fix https://github.com/KittyCAD/modeling-app/issues/5437
 | 
						|
      await page.waitForTimeout(3_000)
 | 
						|
    })
 | 
						|
 | 
						|
    await sketchIsDrawnProperly()
 | 
						|
 | 
						|
    await test.step('Adding a constraint with a variable, and than ctrl-z-ing which will remove the variable again does not break sketch mode', async () => {
 | 
						|
      await expect(async () => {
 | 
						|
        await segment1Click()
 | 
						|
        await editor.expectState({
 | 
						|
          diagnostics: [],
 | 
						|
          activeLines: ['|>line(end = [-0.41,6.99])'],
 | 
						|
          highlightedCode: 'line(end = [-0.41,6.99])',
 | 
						|
        })
 | 
						|
      }).toPass({ timeout: 30_000, intervals: [1500] })
 | 
						|
 | 
						|
      await toolbar.lengthConstraintBtn.click()
 | 
						|
      await cmdBar.progressCmdBar()
 | 
						|
      await editor.expectEditor.toContain('length001 = 7')
 | 
						|
 | 
						|
      // wait for execute defer
 | 
						|
      await page.waitForTimeout(600)
 | 
						|
      await sketchIsDrawnProperly()
 | 
						|
 | 
						|
      await page.keyboard.down('Meta')
 | 
						|
      await page.keyboard.press('KeyZ')
 | 
						|
      await page.keyboard.up('Meta')
 | 
						|
 | 
						|
      await editor.expectEditor.not.toContain('length001 = 7')
 | 
						|
      await sketchIsDrawnProperly()
 | 
						|
    })
 | 
						|
  })
 | 
						|
 | 
						|
  test('can enter sketch when there is an extrude', async ({
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    page,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [-63.43, 193.08])
 | 
						|
  |> line(end = [168.52, 149.87])
 | 
						|
  |> line(end = [190.29, -39.18])
 | 
						|
  |> tangentialArc(endAbsolute = [319.63, 129.65])
 | 
						|
  |> line(end = [-217.65, -21.76])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile003 = startProfile(sketch001, at = [16.79, 38.24])
 | 
						|
  |> angledLine(angle = 0, length = 182.82, tag = $rectangleSegmentA001)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 105.71)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile004 = circle(
 | 
						|
  sketch001,
 | 
						|
  center = [280.45, 47.57],
 | 
						|
  radius = 55.26
 | 
						|
)
 | 
						|
extrude002 = extrude(profile001, length = 50)
 | 
						|
extrude001 = extrude(profile003, length = 5)
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const [pointOnSegment] = scene.makeMouseHelpers(574, 207)
 | 
						|
 | 
						|
    await pointOnSegment()
 | 
						|
    await toolbar.editSketch()
 | 
						|
    // wait for engine animation
 | 
						|
    await page.waitForTimeout(600)
 | 
						|
 | 
						|
    await test.step('check the sketch is still drawn properly', async () => {
 | 
						|
      await Promise.all([
 | 
						|
        scene.expectPixelColor(TEST_COLORS.WHITE, { x: 596, y: 165 }, 15),
 | 
						|
        scene.expectPixelColor(TEST_COLORS.WHITE, { x: 641, y: 220 }, 15),
 | 
						|
        scene.expectPixelColor(TEST_COLORS.WHITE, { x: 763, y: 214 }, 15),
 | 
						|
      ])
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('exit new sketch without drawing anything should not be a problem', async ({
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    cmdBar,
 | 
						|
    page,
 | 
						|
  }) => {
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
        myVar = 5`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
 | 
						|
    await page.waitForTimeout(5000)
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const [selectXZPlane] = scene.makeMouseHelpers(650, 150)
 | 
						|
 | 
						|
    await toolbar.startSketchPlaneSelection()
 | 
						|
    await selectXZPlane()
 | 
						|
    // timeout wait for engine animation is unavoidable
 | 
						|
    await page.waitForTimeout(600)
 | 
						|
 | 
						|
    await editor.expectEditor.toContain(`sketch001 = startSketchOn(XZ)`)
 | 
						|
    await toolbar.exitSketch()
 | 
						|
 | 
						|
    await editor.expectEditor.not.toContain(`sketch001 = startSketchOn(XZ)`)
 | 
						|
 | 
						|
    await test.step("still renders code, hasn't got into a weird state", async () => {
 | 
						|
      await editor.replaceCode(
 | 
						|
        'myVar = 5',
 | 
						|
        `myVar = 5
 | 
						|
  sketch001 = startSketchOn(XZ)
 | 
						|
  profile001 = circle(
 | 
						|
    sketch001,
 | 
						|
    center = [12.41, 3.87],
 | 
						|
    radius = myVar
 | 
						|
  )`
 | 
						|
      )
 | 
						|
 | 
						|
      await scene.settled(cmdBar)
 | 
						|
 | 
						|
      await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('A sketch with only "startProfileAt" and no segments should still be able to be continued', async ({
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    page,
 | 
						|
  }) => {
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [85.19, 338.59])
 | 
						|
  |> line(end = [213.3, -94.52])
 | 
						|
  |> line(end = [-230.09, -55.34])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
sketch002 = startSketchOn(XY)
 | 
						|
profile002 = startProfile(sketch002, at = [85.81, 52.55])
 | 
						|
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const [startProfileAt] = scene.makeMouseHelpers(606, 184)
 | 
						|
    const [nextPoint] = scene.makeMouseHelpers(763, 130)
 | 
						|
    await page.getByText('startProfile(sketch002, at = [85.81, 52.55])').click()
 | 
						|
    await toolbar.editSketch(1)
 | 
						|
    // timeout wait for engine animation is unavoidable
 | 
						|
    await page.waitForTimeout(600)
 | 
						|
 | 
						|
    // equip line tool
 | 
						|
    await toolbar.lineBtn.click()
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await startProfileAt()
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await nextPoint()
 | 
						|
    await editor.expectEditor.toContain(`|> line(end = [126.05, 44.12])`)
 | 
						|
  })
 | 
						|
  test('old style sketch all in one pipe (with extrude) will break up to allow users to add a new profile to the same sketch', async ({
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    page,
 | 
						|
  }) => {
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
thePart = startSketchOn(XZ)
 | 
						|
  |> startProfile(at = [7.53, 10.51])
 | 
						|
  |> line(end = [12.54, 1.83])
 | 
						|
  |> line(end = [6.65, -6.91])
 | 
						|
  |> line(end = [-6.31, -8.69])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
extrude001 = extrude(thePart, length = 75)
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const [objClick] = scene.makeMouseHelpers(565, 343)
 | 
						|
    const [profilePoint1] = scene.makeMouseHelpers(609, 289)
 | 
						|
    const [profilePoint2] = scene.makeMouseHelpers(714, 389)
 | 
						|
 | 
						|
    await test.step('enter sketch and setup', async () => {
 | 
						|
      await objClick()
 | 
						|
      await toolbar.editSketch()
 | 
						|
      // timeout wait for engine animation is unavoidable
 | 
						|
      await page.waitForTimeout(600)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('expect code to match initial conditions still', async () => {
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `thePart = startSketchOn(XZ)  |> startProfile(at = [7.53, 10.51])`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('equiping the line tool should break up the pipe expression', async () => {
 | 
						|
      await toolbar.lineBtn.click()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `sketch001 = startSketchOn(XZ)thePart = startProfile(sketch001, at = [7.53, 10.51])`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('can continue on to add a new profile to this sketch', async () => {
 | 
						|
      await profilePoint1()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `profile001 = startProfile(sketch001, at = [19.69, -7.05])`
 | 
						|
      )
 | 
						|
      await profilePoint2()
 | 
						|
      await editor.expectEditor.toContain(`|> line(end = [18.97, -18.06])`)
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('Can enter sketch on sketch of wall and cap for segment, solid2d, extrude-wall, extrude-cap selections', async ({
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    page,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    // TODO this test should include a test for selecting revolve walls and caps
 | 
						|
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [6.71, -3.66])
 | 
						|
  |> line(end = [2.65, 9.02], tag = $seg02)
 | 
						|
  |> line(end = [3.73, -9.36], tag = $seg01)
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
extrude001 = extrude(profile001, length = 20)
 | 
						|
sketch002 = startSketchOn(extrude001, face = seg01)
 | 
						|
profile002 = startProfile(sketch002, at = [0.75, 13.46])
 | 
						|
  |> line(end = [4.52, 3.79])
 | 
						|
  |> line(end = [5.98, -2.81])
 | 
						|
profile003 = startProfile(sketch002, at = [3.19, 13.3])
 | 
						|
  |> angledLine(angle = 0, length = 6.64, tag = $rectangleSegmentA001)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 2.81)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile004 = startProfile(sketch002, at = [3.15, 9.39])
 | 
						|
  |> xLine(length = 6.92)
 | 
						|
  |> line(end = [-7.41, -2.85])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile005 = circle(sketch002, center = [5.15, 4.34], radius = 1.66)
 | 
						|
profile006 = startProfile(sketch002, at = [9.65, 3.82])
 | 
						|
  |> line(end = [2.38, 5.62])
 | 
						|
  |> line(end = [2.13, -5.57])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
revolve001 = revolve(
 | 
						|
  profile004,
 | 
						|
  angle = 45,
 | 
						|
  axis = getNextAdjacentEdge(seg01)
 | 
						|
)
 | 
						|
extrude002 = extrude(profile006, length = 4)
 | 
						|
sketch003 = startSketchOn(-XZ)
 | 
						|
profile007 = startProfile(sketch003, at = [4.8, 7.55])
 | 
						|
  |> line(end = [7.39, 2.58])
 | 
						|
  |> line(end = [7.02, -2.85])
 | 
						|
profile008 = startProfile(sketch003, at = [5.54, 5.49])
 | 
						|
  |> line(end = [6.34, 2.64])
 | 
						|
  |> line(end = [6.33, -2.96])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile009 = startProfile(sketch003, at = [5.23, 1.95])
 | 
						|
  |> line(end = [6.8, 2.17])
 | 
						|
  |> line(end = [7.34, -2.75])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile010 = circle(
 | 
						|
  sketch003,
 | 
						|
  center = [7.18, -2.11],
 | 
						|
  radius = 2.67
 | 
						|
)
 | 
						|
profile011 = startProfile(sketch003, at = [5.07, -6.39])
 | 
						|
  |> angledLine(angle = 0, length = 4.54, tag = $rectangleSegmentA002)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA002) - 90, length = 4.17)
 | 
						|
  |> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002))
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
extrude003 = extrude(profile011, length = 2.5)
 | 
						|
// TODO this breaks the test,
 | 
						|
// revolve002 = revolve(profile008, angle = 45, axis = seg02)
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const camPositionForSelectingSketchOnWallProfiles = () =>
 | 
						|
      scene.moveCameraTo(
 | 
						|
        { x: 834, y: -680, z: 534 },
 | 
						|
        { x: -54, y: -476, z: 148 }
 | 
						|
      )
 | 
						|
    const wallSelectionOptions = [
 | 
						|
      {
 | 
						|
        title: 'select wall segment',
 | 
						|
        selectClick: scene.makeMouseHelpers(598, 211)[0],
 | 
						|
      },
 | 
						|
      {
 | 
						|
        title: 'select wall solid 2d',
 | 
						|
        selectClick: scene.makeMouseHelpers(677, 236)[0],
 | 
						|
      },
 | 
						|
      {
 | 
						|
        title: 'select wall circle',
 | 
						|
        selectClick: scene.makeMouseHelpers(811, 247)[0],
 | 
						|
      },
 | 
						|
      {
 | 
						|
        title: 'select wall extrude wall',
 | 
						|
        selectClick: scene.makeMouseHelpers(793, 136)[0],
 | 
						|
      },
 | 
						|
      {
 | 
						|
        title: 'select wall extrude cap',
 | 
						|
        selectClick: scene.makeMouseHelpers(836, 103)[0],
 | 
						|
      },
 | 
						|
    ] as const
 | 
						|
 | 
						|
    const verifyWallProfilesAreDrawn = async () =>
 | 
						|
      test.step('verify wall profiles are drawn', async () => {
 | 
						|
        await Promise.all([
 | 
						|
          // open polygon
 | 
						|
          scene.expectPixelColor(TEST_COLORS.WHITE, { x: 599, y: 168 }, 15),
 | 
						|
          // closed polygon
 | 
						|
          scene.expectPixelColor(TEST_COLORS.WHITE, { x: 656, y: 171 }, 15),
 | 
						|
          // revolved profile
 | 
						|
          scene.expectPixelColor(TEST_COLORS.WHITE, { x: 655, y: 264 }, 15),
 | 
						|
          // extruded profile
 | 
						|
          scene.expectPixelColor(TEST_COLORS.WHITE, { x: 808, y: 396 }, 15),
 | 
						|
          // circle (When entering via the circle, it's selected and therefore blue)
 | 
						|
          scene.expectPixelColor(
 | 
						|
            [TEST_COLORS.WHITE, TEST_COLORS.BLUE],
 | 
						|
            { x: 742, y: 386 },
 | 
						|
            15
 | 
						|
          ),
 | 
						|
        ])
 | 
						|
      })
 | 
						|
 | 
						|
    await test.step('select wall profiles', async () => {
 | 
						|
      for (const { title, selectClick } of wallSelectionOptions) {
 | 
						|
        await test.step(title, async () => {
 | 
						|
          await camPositionForSelectingSketchOnWallProfiles()
 | 
						|
          await selectClick()
 | 
						|
          await toolbar.editSketch(1)
 | 
						|
          await page.waitForTimeout(600)
 | 
						|
          await verifyWallProfilesAreDrawn()
 | 
						|
          await toolbar.exitSketchBtn.click()
 | 
						|
          await page.waitForTimeout(100)
 | 
						|
        })
 | 
						|
      }
 | 
						|
    })
 | 
						|
 | 
						|
    /* FIXME: the cap part of this test is insanely flaky, and I'm not sure
 | 
						|
       * why.
 | 
						|
       * await test.step('select cap profiles', async () => {
 | 
						|
        for (const { title, selectClick } of capSelectionOptions) {
 | 
						|
          await test.step(title, async () => {
 | 
						|
            await camPositionForSelectingSketchOnCapProfiles()
 | 
						|
            await page.waitForTimeout(100)
 | 
						|
            await selectClick()
 | 
						|
            await toolbar.editSketch()
 | 
						|
            await page.waitForTimeout(600)
 | 
						|
            await verifyCapProfilesAreDrawn()
 | 
						|
            await toolbar.exitSketchBtn.click()
 | 
						|
            await page.waitForTimeout(100)
 | 
						|
          })
 | 
						|
        }
 | 
						|
      }) */
 | 
						|
  })
 | 
						|
  test('Can enter sketch loft edges, base and continue sketch', async ({
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    page,
 | 
						|
  }) => {
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `@settings(defaultLengthUnit = in)
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [34, 42.66])
 | 
						|
  |> line(end = [102.65, 151.99])
 | 
						|
  |> line(end = [76, -138.66])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
plane001 = offsetPlane(XZ, offset = 50)
 | 
						|
sketch002 = startSketchOn(plane001)
 | 
						|
profile002 = startProfile(sketch002, at = [39.43, 172.21])
 | 
						|
  |> xLine(length = 183.99)
 | 
						|
  |> line(end = [-77.95, -145.93])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
 | 
						|
loft([profile001, profile002])
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const [baseProfileEdgeClick] = scene.makeMouseHelpers(621, 292)
 | 
						|
 | 
						|
    const [rect1Crn1] = scene.makeMouseHelpers(592, 283)
 | 
						|
    const [rect1Crn2] = scene.makeMouseHelpers(797, 268)
 | 
						|
 | 
						|
    await baseProfileEdgeClick()
 | 
						|
    await toolbar.editSketch()
 | 
						|
    await page.waitForTimeout(600)
 | 
						|
    await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 562, y: 172 }, 15)
 | 
						|
 | 
						|
    await toolbar.rectangleBtn.click()
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await rect1Crn1()
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `profile003 = startProfile(sketch001, at = [50.72, -18.19])`
 | 
						|
    )
 | 
						|
    await rect1Crn2()
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `angledLine(angle = 0, length = 113.01, tag = $rectangleSegmentA001)`
 | 
						|
    )
 | 
						|
  })
 | 
						|
  test('Can enter sketch loft edges offsetPlane and continue sketch', async ({
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
  }) => {
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [34, 42.66])
 | 
						|
  |> line(end = [102.65, 151.99])
 | 
						|
  |> line(end = [76, -138.66])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
plane001 = offsetPlane(XZ, offset = 50)
 | 
						|
sketch002 = startSketchOn(plane001)
 | 
						|
profile002 = startProfile(sketch002, at = [39.43, 172.21])
 | 
						|
  |> xLine(length = 183.99)
 | 
						|
  |> line(end = [-77.95, -145.93])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
 | 
						|
loft([profile001, profile002])
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    const topProfileEdgeClickCoords = { x: 602, y: 185 } as const
 | 
						|
    const [topProfileEdgeClick] = scene.makeMouseHelpers(
 | 
						|
      topProfileEdgeClickCoords.x,
 | 
						|
      topProfileEdgeClickCoords.y
 | 
						|
    )
 | 
						|
    const [sideProfileEdgeClick] = scene.makeMouseHelpers(788, 188)
 | 
						|
 | 
						|
    const [rect1Crn1] = scene.makeMouseHelpers(592, 283)
 | 
						|
    const [rect1Crn2] = scene.makeMouseHelpers(797, 268)
 | 
						|
 | 
						|
    await scene.moveCameraTo(
 | 
						|
      { x: 8171, y: -7740, z: 1624 },
 | 
						|
      { x: 3302, y: -627, z: 2892 }
 | 
						|
    )
 | 
						|
 | 
						|
    await topProfileEdgeClick()
 | 
						|
    await page.waitForTimeout(300)
 | 
						|
    await toolbar.editSketch()
 | 
						|
    await page.waitForTimeout(600)
 | 
						|
    await sideProfileEdgeClick()
 | 
						|
    await page.waitForTimeout(300)
 | 
						|
    await scene.expectPixelColor(TEST_COLORS.BLUE, { x: 788, y: 188 }, 15)
 | 
						|
 | 
						|
    await toolbar.rectangleBtn.click()
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
    await rect1Crn1()
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `profile003 = startProfile(plane001, at = [47.76, -17.13])`
 | 
						|
    )
 | 
						|
    await rect1Crn2()
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `angledLine(angle = 0, length = 106.42], tag = $rectangleSegmentA001)`
 | 
						|
    )
 | 
						|
    await page.waitForTimeout(100)
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
// Regression test for https://github.com/KittyCAD/modeling-app/issues/4891
 | 
						|
test.describe(`Click based selection don't brick the app when clicked out of range after format using cache`, () => {
 | 
						|
  test(`Can select a line that reformmed after entering sketch mode`, async ({
 | 
						|
    context,
 | 
						|
    page,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    homePage,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    // We seed the scene with a single offset plane
 | 
						|
    await context.addInitScript(() => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `sketch001 = startSketchOn(XZ)
 | 
						|
  |> startProfile(at = [0, 0])
 | 
						|
  |> line(end = [3.14, 3.14])
 | 
						|
  |> arc(
 | 
						|
       interiorAbsolute = [1, 2],
 | 
						|
       endAbsolute = [4, 2]
 | 
						|
     )`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    const formattedArc = `arc(interiorAbsolute = [1, 2], endAbsolute = [4, 2])`
 | 
						|
    await test.step(`format the code`, async () => {
 | 
						|
      // doesn't contain condensed version
 | 
						|
      await editor.expectEditor.not.toContain(formattedArc)
 | 
						|
      // click the code to enter sketch mode
 | 
						|
      await page.getByText(`arc`).click()
 | 
						|
      // Format the code.
 | 
						|
      await page.locator('#code-pane button:first-child').click()
 | 
						|
      await page.locator('button:has-text("Format code")').click()
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step(`Ensure the code reformatted`, async () => {
 | 
						|
      await editor.expectEditor.toContain(formattedArc)
 | 
						|
    })
 | 
						|
 | 
						|
    const [arcClick, arcHover] = scene.makeMouseHelpers(699, 337)
 | 
						|
    await test.step('Ensure we can hover the arc', async () => {
 | 
						|
      await arcHover()
 | 
						|
 | 
						|
      // Check that the code is highlighted
 | 
						|
      await editor.expectState({
 | 
						|
        activeLines: ['sketch001=startSketchOn(XZ)'],
 | 
						|
        diagnostics: [],
 | 
						|
        highlightedCode: 'arc(interiorAbsolute = [1, 2], endAbsolute = [4, 2])',
 | 
						|
      })
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('reset the selection', async () => {
 | 
						|
      // Move the mouse out of the way
 | 
						|
      await page.mouse.move(655, 337)
 | 
						|
 | 
						|
      await editor.expectState({
 | 
						|
        activeLines: ['sketch001=startSketchOn(XZ)'],
 | 
						|
        diagnostics: [],
 | 
						|
        highlightedCode: '',
 | 
						|
      })
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('Ensure we can click the arc', async () => {
 | 
						|
      await arcClick()
 | 
						|
 | 
						|
      // Check that the code is highlighted
 | 
						|
      await editor.expectState({
 | 
						|
        activeLines: [],
 | 
						|
        diagnostics: [],
 | 
						|
        highlightedCode: 'arc(interiorAbsolute = [1, 2], endAbsolute = [4, 2])',
 | 
						|
      })
 | 
						|
    })
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
// Regression test for https://github.com/KittyCAD/modeling-app/issues/4372
 | 
						|
test.describe('Redirecting to home page and back to the original file should clear sketch DOM elements', () => {
 | 
						|
  test('Can redirect to home page and back to original file and have a cleared DOM', async ({
 | 
						|
    context,
 | 
						|
    page,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    editor,
 | 
						|
    homePage,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    // We seed the scene with a single offset plane
 | 
						|
    await context.addInitScript(() => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        ` sketch001 = startSketchOn(XZ)
 | 
						|
|> startProfile(at = [256.85, 14.41])
 | 
						|
|> line(endAbsolute = [0, 211.07])
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    const [objClick] = scene.makeMouseHelpers(634, 274)
 | 
						|
    await objClick()
 | 
						|
 | 
						|
    // Enter sketch mode
 | 
						|
    await toolbar.editSketch()
 | 
						|
 | 
						|
    await expect(page.getByText('323.49')).toBeVisible()
 | 
						|
 | 
						|
    // Open navigation side bar
 | 
						|
    await page.getByTestId('project-sidebar-toggle').click()
 | 
						|
    const goToHome = page.getByRole('button', {
 | 
						|
      name: 'Go to Home',
 | 
						|
    })
 | 
						|
 | 
						|
    await goToHome.click()
 | 
						|
    await homePage.openProject('testDefault')
 | 
						|
    await expect(page.getByText('323.49')).not.toBeVisible()
 | 
						|
  })
 | 
						|
 | 
						|
  test('Straight line snapping to previous tangent', async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
    toolbar,
 | 
						|
    scene,
 | 
						|
    cmdBar,
 | 
						|
    context,
 | 
						|
    editor,
 | 
						|
  }) => {
 | 
						|
    await context.addInitScript(() => {
 | 
						|
      localStorage.setItem('persistCode', `@settings(defaultLengthUnit = mm)`)
 | 
						|
    })
 | 
						|
 | 
						|
    const viewportSize = { width: 1200, height: 900 }
 | 
						|
    await page.setBodyDimensions(viewportSize)
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
 | 
						|
    // wait until scene is ready to be interacted with
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
						|
 | 
						|
    // select an axis plane
 | 
						|
    await page.mouse.click(700, 200)
 | 
						|
 | 
						|
    // Needed as we don't yet have a way to get a signal from the engine that the camera has animated to the sketch plane
 | 
						|
    await page.waitForTimeout(3000)
 | 
						|
 | 
						|
    const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
 | 
						|
    const { click00r } = getMovementUtils({ center, page })
 | 
						|
 | 
						|
    // Draw line
 | 
						|
    await click00r(0, 0)
 | 
						|
    await click00r(200, -200)
 | 
						|
 | 
						|
    // Draw arc
 | 
						|
    await toolbar.selectTangentialArc()
 | 
						|
    await click00r(0, 0)
 | 
						|
    await click00r(100, 100)
 | 
						|
 | 
						|
    // Switch back to line
 | 
						|
    await toolbar.selectLine()
 | 
						|
    await click00r(0, 0)
 | 
						|
    await click00r(-100, 100)
 | 
						|
 | 
						|
    // Draw a 3 point arc
 | 
						|
    await toolbar.selectThreePointArc()
 | 
						|
    await click00r(0, 0)
 | 
						|
    await click00r(0, 100)
 | 
						|
    await click00r(100, 0)
 | 
						|
 | 
						|
    // draw a line to opposite tangent direction of previous arc
 | 
						|
    await toolbar.selectLine()
 | 
						|
    await click00r(0, 0)
 | 
						|
    await click00r(-200, 200)
 | 
						|
 | 
						|
    await editor.expectEditor.toContain(
 | 
						|
      `@settings(defaultLengthUnit = mm)
 | 
						|
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [0, 0])
 | 
						|
  |> line(end = [191.39, 191.39])
 | 
						|
  |> tangentialArc(end = [95.69, -95.7], tag = $seg01)
 | 
						|
  |> angledLine(angle = tangentToEnd(seg01), length = 135.34)
 | 
						|
  |> arc(interiorAbsolute = [191.39, -95.69], endAbsolute = [287.08, -95.69], tag = $seg02)
 | 
						|
  |> angledLine(angle = tangentToEnd(seg02) + turns::HALF_TURN, length = 270.67)
 | 
						|
`.replaceAll('\n', '')
 | 
						|
    )
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
test.describe('manual edits during sketch mode', () => {
 | 
						|
  test('Can edit sketch through feature tree with variable modifications', async ({
 | 
						|
    page,
 | 
						|
    context,
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    editor,
 | 
						|
    toolbar,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const initialCode = `myVar1 = 5
 | 
						|
    myVar2 = 6
 | 
						|
 | 
						|
    sketch001 = startSketchOn(XZ)
 | 
						|
    profile001 = startProfile(sketch001, at = [106.68, 89.77])
 | 
						|
      |> line(end = [132.34, 157.8])
 | 
						|
      |> line(end = [67.65, -460.55], tag = $seg01)
 | 
						|
      |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
      |> close()
 | 
						|
    extrude001 = extrude(profile001, length = 500)
 | 
						|
    sketch002 = startSketchOn(extrude001, face = seg01)
 | 
						|
    profile002 = startProfile(sketch002, at = [83.39, 329.15])
 | 
						|
      |> angledLine(angle = 0, length = 119.61, tag = $rectangleSegmentA001)
 | 
						|
      |> angledLine(length = 156.54, angle = -28)
 | 
						|
      |> angledLine(
 | 
						|
          angle = -151,
 | 
						|
          length = 116.27,
 | 
						|
        )
 | 
						|
      |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
      |> close()
 | 
						|
    profile003 = startProfile(sketch002, at = [-201.08, 254.17])
 | 
						|
      |> line(end = [103.55, 33.32])
 | 
						|
      |> line(end = [48.8, -153.54])`
 | 
						|
 | 
						|
    await context.addInitScript((initialCode) => {
 | 
						|
      localStorage.setItem('persistCode', initialCode)
 | 
						|
    }, initialCode)
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await test.step('Open feature tree and edit second sketch', async () => {
 | 
						|
      await toolbar.openFeatureTreePane()
 | 
						|
      const sketchButton = await toolbar.getFeatureTreeOperation('Sketch', 1)
 | 
						|
      await sketchButton.dblclick()
 | 
						|
      await page.waitForTimeout(700) // Wait for engine animation
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('Add new variable and wait for re-execution', async () => {
 | 
						|
      await page.waitForTimeout(500) // wait for deferred execution
 | 
						|
      await editor.replaceCode('myVar2 = 6', 'myVar2 = 6\nmyVar3 = 7')
 | 
						|
      await page.waitForTimeout(2000) // wait for deferred execution
 | 
						|
    })
 | 
						|
 | 
						|
    const handle1Location = { x: 843, y: 235 }
 | 
						|
    await test.step('Edit sketch by dragging handle', async () => {
 | 
						|
      await page.waitForTimeout(500)
 | 
						|
      await expect
 | 
						|
        .poll(
 | 
						|
          async () => {
 | 
						|
            await editor.expectEditor.toContain('length = 156.54, angle = -28')
 | 
						|
            await page.mouse.move(handle1Location.x, handle1Location.y)
 | 
						|
            await page.mouse.down()
 | 
						|
            await page.mouse.move(
 | 
						|
              handle1Location.x + 50,
 | 
						|
              handle1Location.y + 50,
 | 
						|
              {
 | 
						|
                steps: 5,
 | 
						|
              }
 | 
						|
            )
 | 
						|
            await page.mouse.up()
 | 
						|
            await editor.expectEditor.toContain('length = 231.59, angle = -34')
 | 
						|
            return true
 | 
						|
          },
 | 
						|
          { timeout: 10_000 }
 | 
						|
        )
 | 
						|
        .toBeTruthy()
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('Delete variables and wait for re-execution', async () => {
 | 
						|
      await page.waitForTimeout(500)
 | 
						|
      await editor.replaceCode('myVar3 = 7', '')
 | 
						|
      await page.waitForTimeout(50)
 | 
						|
      await editor.replaceCode('myVar2 = 6', '')
 | 
						|
      await page.waitForTimeout(2000) // Wait for deferred execution
 | 
						|
    })
 | 
						|
 | 
						|
    const handle2Location = { x: 872, y: 273 }
 | 
						|
    await test.step('Edit sketch again', async () => {
 | 
						|
      await editor.expectEditor.toContain('length = 231.59, angle = -34')
 | 
						|
      await page.waitForTimeout(500)
 | 
						|
      await expect
 | 
						|
        .poll(
 | 
						|
          async () => {
 | 
						|
            await page.mouse.move(handle2Location.x, handle2Location.y)
 | 
						|
            await page.mouse.down()
 | 
						|
            await page.mouse.move(handle2Location.x, handle2Location.y - 50, {
 | 
						|
              steps: 5,
 | 
						|
            })
 | 
						|
            await page.mouse.up()
 | 
						|
            await editor.expectEditor.toContain('length = 167.36, angle = -14')
 | 
						|
            return true
 | 
						|
          },
 | 
						|
          { timeout: 10_000 }
 | 
						|
        )
 | 
						|
        .toBeTruthy()
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('add whole other sketch before current sketch', async () => {
 | 
						|
      await page.waitForTimeout(500)
 | 
						|
      await editor.replaceCode(
 | 
						|
        `myVar1 = 5`,
 | 
						|
        `myVar1 = 5
 | 
						|
    sketch003 = startSketchOn(XY)
 | 
						|
    profile004 = circle(sketch003, center = [143.91, 136.89], radius = 71.63)`
 | 
						|
      )
 | 
						|
      await page.waitForTimeout(2000) // Wait for deferred execution
 | 
						|
    })
 | 
						|
 | 
						|
    const handle3Location = { x: 844, y: 212 }
 | 
						|
    await test.step('edit sketch again', async () => {
 | 
						|
      await page.waitForTimeout(500) // Wait for deferred execution
 | 
						|
      await expect
 | 
						|
        .poll(
 | 
						|
          async () => {
 | 
						|
            await editor.expectEditor.toContain('length = 167.36, angle = -14')
 | 
						|
            await page.mouse.move(handle3Location.x, handle3Location.y)
 | 
						|
            await page.mouse.down()
 | 
						|
            await page.mouse.move(handle3Location.x, handle3Location.y + 110, {
 | 
						|
              steps: 5,
 | 
						|
            })
 | 
						|
            await page.mouse.up()
 | 
						|
            await editor.expectEditor.toContain('length = 219.2, angle = -56')
 | 
						|
            return true
 | 
						|
          },
 | 
						|
          { timeout: 10_000 }
 | 
						|
        )
 | 
						|
        .toBeTruthy()
 | 
						|
    })
 | 
						|
 | 
						|
    // exit sketch and assert whole code
 | 
						|
    await test.step('Exit sketch and assert code', async () => {
 | 
						|
      await toolbar.exitSketch()
 | 
						|
      await editor.expectEditor.toContain(
 | 
						|
        `myVar1 = 5
 | 
						|
sketch003 = startSketchOn(XY)
 | 
						|
profile004 = circle(sketch003, center = [143.91, 136.89], radius = 71.63)
 | 
						|
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [106.68, 89.77])
 | 
						|
  |> line(end = [132.34, 157.8])
 | 
						|
  |> line(end = [67.65, -460.55], tag = $seg01)
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
extrude001 = extrude(profile001, length = 500)
 | 
						|
sketch002 = startSketchOn(extrude001, face = seg01)
 | 
						|
profile002 = startProfile(sketch002, at = [83.39, 329.15])
 | 
						|
  |> angledLine(angle = 0, length = 119.61, tag = $rectangleSegmentA001)
 | 
						|
  |> angledLine(length = 219.2, angle = -56)
 | 
						|
  |> angledLine(angle = -151, length = 116.27)
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
profile003 = startProfile(sketch002, at = [-201.08, 254.17])
 | 
						|
  |> line(end = [103.55, 33.32])
 | 
						|
  |> line(end = [48.8, -153.54])
 | 
						|
`,
 | 
						|
        { shouldNormalise: true }
 | 
						|
      )
 | 
						|
      await editor.expectState({
 | 
						|
        activeLines: [],
 | 
						|
        diagnostics: [],
 | 
						|
        highlightedCode: '',
 | 
						|
      })
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('Will exit out of sketch mode for some incompatible edits', async ({
 | 
						|
    page,
 | 
						|
    context,
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    editor,
 | 
						|
    toolbar,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const initialCode = `myVar1 = 5
 | 
						|
    myVar2 = 6
 | 
						|
 | 
						|
    sketch001 = startSketchOn(XZ)
 | 
						|
    profile001 = startProfile(sketch001, at = [106.68, 89.77])
 | 
						|
      |> line(end = [132.34, 157.8])
 | 
						|
      |> line(end = [67.65, -460.55], tag = $seg01)
 | 
						|
      |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
      |> close()
 | 
						|
    extrude001 = extrude(profile001, length = 500)
 | 
						|
    sketch002 = startSketchOn(extrude001, face = seg01)
 | 
						|
    profile002 = startProfile(sketch002, at = [83.39, 329.15])
 | 
						|
      |> angledLine(angle = 0, length = 119.61, tag = $rectangleSegmentA001)
 | 
						|
      |> angledLine(length = 156.54, angle = -28)
 | 
						|
      |> angledLine(
 | 
						|
           angle = -151,
 | 
						|
           length = 116.27,
 | 
						|
         )
 | 
						|
      |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
      |> close()
 | 
						|
    profile003 = startProfile(sketch002, at = [-201.08, 254.17])
 | 
						|
      |> line(end = [103.55, 33.32])
 | 
						|
      |> line(end = [48.8, -153.54])`
 | 
						|
 | 
						|
    await context.addInitScript((initialCode) => {
 | 
						|
      localStorage.setItem('persistCode', initialCode)
 | 
						|
    }, initialCode)
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
    const expectSketchOriginToBeDrawn = async () => {
 | 
						|
      await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 672, y: 193 }, 15)
 | 
						|
    }
 | 
						|
 | 
						|
    await test.step('Open feature tree and edit second sketch', async () => {
 | 
						|
      await toolbar.openFeatureTreePane()
 | 
						|
      const sketchButton = await toolbar.getFeatureTreeOperation('Sketch', 1)
 | 
						|
      await sketchButton.dblclick()
 | 
						|
      await page.waitForTimeout(700) // Wait for engine animation
 | 
						|
      await expectSketchOriginToBeDrawn()
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('rename variable of current sketch, sketch002 to changeSketchNamePartWayThrough', async () => {
 | 
						|
      await editor.replaceCode('sketch002', 'changeSketchNamePartWayThrough')
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      // three times to rename the declaration and it's use
 | 
						|
      await editor.replaceCode('sketch002', 'changeSketchNamePartWayThrough')
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await editor.replaceCode('sketch002', 'changeSketchNamePartWayThrough')
 | 
						|
      await expect(
 | 
						|
        page.getByText('Unable to maintain sketch mode')
 | 
						|
      ).toBeVisible()
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('Will exit out of sketch mode when all code is nuked', async ({
 | 
						|
    page,
 | 
						|
    context,
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    editor,
 | 
						|
    toolbar,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const initialCode = `myVar1 = 5
 | 
						|
    myVar2 = 6
 | 
						|
 | 
						|
    sketch001 = startSketchOn(XZ)
 | 
						|
    profile001 = startProfile(sketch001, at = [106.68, 89.77])
 | 
						|
      |> line(end = [132.34, 157.8])
 | 
						|
      |> line(end = [67.65, -460.55], tag = $seg01)
 | 
						|
      |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
      |> close()
 | 
						|
    extrude001 = extrude(profile001, length = 500)
 | 
						|
    sketch002 = startSketchOn(extrude001, face = seg01)
 | 
						|
    profile002 = startProfile(sketch002, at = [83.39, 329.15])
 | 
						|
      |> angledLine(angle = 0, length = 119.61, tag = $rectangleSegmentA001)
 | 
						|
      |> angledLine(length = 156.54, angle = -28)
 | 
						|
      |> angledLine(
 | 
						|
           angle = -151,
 | 
						|
           length = 116.27,
 | 
						|
         )
 | 
						|
      |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
      |> close()
 | 
						|
    profile003 = startProfile(sketch002, at = [-201.08, 254.17])
 | 
						|
      |> line(end = [103.55, 33.32])
 | 
						|
      |> line(end = [48.8, -153.54])`
 | 
						|
 | 
						|
    await context.addInitScript((initialCode) => {
 | 
						|
      localStorage.setItem('persistCode', initialCode)
 | 
						|
    }, initialCode)
 | 
						|
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
    const expectSketchOriginToBeDrawn = async () => {
 | 
						|
      await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 672, y: 193 }, 15)
 | 
						|
    }
 | 
						|
 | 
						|
    await test.step('Open feature tree and edit second sketch', async () => {
 | 
						|
      await toolbar.openFeatureTreePane()
 | 
						|
      const sketchButton = await toolbar.getFeatureTreeOperation('Sketch', 1)
 | 
						|
      await sketchButton.dblclick()
 | 
						|
      await page.waitForTimeout(700) // Wait for engine animation
 | 
						|
      await expectSketchOriginToBeDrawn()
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('clear editor content while in sketch mode', async () => {
 | 
						|
      await editor.replaceCode('', '')
 | 
						|
      await page.waitForTimeout(100)
 | 
						|
      await expect(
 | 
						|
        page.getByText('Unable to maintain sketch mode')
 | 
						|
      ).toBeVisible()
 | 
						|
      await scene.expectPixelColorNotToBe(
 | 
						|
        TEST_COLORS.WHITE,
 | 
						|
        { x: 672, y: 193 },
 | 
						|
        15
 | 
						|
      )
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('empty draft sketch is cleaned up properly', async ({
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    cmdBar,
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
  }) => {
 | 
						|
    // This is the sketch used in the original report, but any sketch would work
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `yRel002 = 200
 | 
						|
lStraight = -200
 | 
						|
yRel001 = -lStraight
 | 
						|
length001 = lStraight
 | 
						|
sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [-102.72, 237.44])
 | 
						|
  |> yLine(length = lStraight)
 | 
						|
  |> tangentialArc(endAbsolute = [118.9, 23.57])
 | 
						|
  |> line(end = [-17.64, yRel002])
 | 
						|
`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
    await scene.connectionEstablished()
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    // Ensure start sketch button is enabled
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Start Sketch' })
 | 
						|
    ).not.toBeDisabled()
 | 
						|
 | 
						|
    // Start a new sketch
 | 
						|
    const [selectXZPlane] = scene.makeMouseHelpers(650, 150)
 | 
						|
    await toolbar.startSketchPlaneSelection()
 | 
						|
    await selectXZPlane()
 | 
						|
    await page.waitForTimeout(2000) // wait for engine animation
 | 
						|
 | 
						|
    // Switch to a different tool (circle)
 | 
						|
    await toolbar.circleBtn.click()
 | 
						|
    await expect(toolbar.circleBtn).toHaveAttribute('aria-pressed', 'true')
 | 
						|
 | 
						|
    // Exit the empty sketch
 | 
						|
    await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
						|
 | 
						|
    // Ensure the feature tree now shows only one sketch
 | 
						|
    await toolbar.openFeatureTreePane()
 | 
						|
    await expect(
 | 
						|
      toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
 | 
						|
    ).toHaveCount(1)
 | 
						|
    await toolbar.closeFeatureTreePane()
 | 
						|
 | 
						|
    // Open the first sketch from the feature tree (the existing sketch)
 | 
						|
    await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
 | 
						|
    // timeout is a bit longer because when the bug happened, it did go into sketch mode for a split second, but returned
 | 
						|
    // automatically, we want to make sure it stays there.
 | 
						|
    await page.waitForTimeout(2000)
 | 
						|
 | 
						|
    // Validate we are in sketch mode (Exit Sketch button visible)
 | 
						|
    await expect(
 | 
						|
      page.getByRole('button', { name: 'Exit Sketch' })
 | 
						|
    ).toBeVisible()
 | 
						|
  })
 | 
						|
 | 
						|
  // Ensure feature tree is not showing previous file's content when switching to a file with KCL errors.
 | 
						|
  test('Feature tree shows correct sketch count per file', async ({
 | 
						|
    context,
 | 
						|
    homePage,
 | 
						|
    scene,
 | 
						|
    toolbar,
 | 
						|
    cmdBar,
 | 
						|
    page,
 | 
						|
  }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
 | 
						|
    // Setup project with files.
 | 
						|
    const GOOD_KCL = `sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [220.81, 253.8])
 | 
						|
  |> line(end = [132.84, -151.31])
 | 
						|
  |> line(end = [25.51, 167.15])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()
 | 
						|
sketch002 = startSketchOn(XZ)
 | 
						|
profile002 = startProfile(sketch002, at = [158.35, -70.82])
 | 
						|
  |> line(end = [73.9, -152.19])
 | 
						|
  |> line(end = [85.33, 135.48])
 | 
						|
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
						|
  |> close()`
 | 
						|
 | 
						|
    const ERROR_KCL = `sketch001 = startSketchOn(XZ)
 | 
						|
profile001 = startProfile(sketch001, at = [127.56, 179.02])
 | 
						|
  |> line(end = [132.84, -112.6])
 | 
						|
  |> line(end = [85.33, 234.01])
 | 
						|
  |> line(enfd = [-137.23, -54.55])`
 | 
						|
 | 
						|
    await context.folderSetupFn(async (dir) => {
 | 
						|
      const projectDir = path.join(dir, 'multi-file-sketch-test')
 | 
						|
      await fs.mkdir(projectDir, { recursive: true })
 | 
						|
      await Promise.all([
 | 
						|
        fs.writeFile(path.join(projectDir, 'good.kcl'), GOOD_KCL, 'utf-8'),
 | 
						|
        fs.writeFile(path.join(projectDir, 'error.kcl'), ERROR_KCL, 'utf-8'),
 | 
						|
      ])
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setBodyDimensions({ width: 1000, height: 800 })
 | 
						|
 | 
						|
    await homePage.openProject('multi-file-sketch-test')
 | 
						|
    await scene.connectionEstablished()
 | 
						|
 | 
						|
    await u.closeDebugPanel()
 | 
						|
 | 
						|
    await toolbar.openFeatureTreePane()
 | 
						|
    await toolbar.openPane('files')
 | 
						|
 | 
						|
    await toolbar.openFile('good.kcl')
 | 
						|
 | 
						|
    await expect(
 | 
						|
      toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
 | 
						|
    ).toHaveCount(2)
 | 
						|
 | 
						|
    await toolbar.openFile('error.kcl')
 | 
						|
 | 
						|
    await expect(
 | 
						|
      toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
 | 
						|
    ).toHaveCount(0)
 | 
						|
  })
 | 
						|
 | 
						|
  test('adding a syntax error, recovers after fixing', async ({
 | 
						|
    page,
 | 
						|
    homePage,
 | 
						|
    context,
 | 
						|
    scene,
 | 
						|
    editor,
 | 
						|
    toolbar,
 | 
						|
    cmdBar,
 | 
						|
  }) => {
 | 
						|
    const file = await fs.readFile(
 | 
						|
      path.resolve(
 | 
						|
        __dirname,
 | 
						|
        '../../',
 | 
						|
        './rust/kcl-lib/e2e/executor/inputs/e2e-can-sketch-on-chamfer.kcl'
 | 
						|
      ),
 | 
						|
      'utf-8'
 | 
						|
    )
 | 
						|
    await context.addInitScript((file) => {
 | 
						|
      localStorage.setItem('persistCode', file)
 | 
						|
    }, file)
 | 
						|
    await homePage.goToModelingScene()
 | 
						|
 | 
						|
    const [objClick] = scene.makeMouseHelpers(600, 250)
 | 
						|
    const arrowHeadLocation = { x: 706, y: 129 } as const
 | 
						|
    const arrowHeadWhite = TEST_COLORS.WHITE
 | 
						|
    const backgroundGray: [number, number, number] = [28, 28, 28]
 | 
						|
    const verifyArrowHeadColor = async (c: [number, number, number]) =>
 | 
						|
      scene.expectPixelColor(c, arrowHeadLocation, 15)
 | 
						|
 | 
						|
    // wait for scene to load
 | 
						|
    await scene.settled(cmdBar)
 | 
						|
 | 
						|
    await test.step('check chamfer selection changes cursor position', async () => {
 | 
						|
      await expect(async () => {
 | 
						|
        // sometimes initial click doesn't register
 | 
						|
        await objClick()
 | 
						|
        await editor.expectActiveLinesToBe([
 | 
						|
          '|> startProfile(at = [75.8, 317.2]) // [$startCapTag, $EndCapTag]',
 | 
						|
        ])
 | 
						|
      }).toPass({ timeout: 15_000, intervals: [500] })
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('enter sketch and sanity check segments have been drawn', async () => {
 | 
						|
      await toolbar.editSketch()
 | 
						|
      // this checks sketch segments have been drawn
 | 
						|
      await verifyArrowHeadColor(arrowHeadWhite)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('Make typo and check the segments have Disappeared and there is a syntax error', async () => {
 | 
						|
      await editor.replaceCode(
 | 
						|
        'line(endAbsolute = [pro',
 | 
						|
        'badBadBadFn(endAbsolute = [pro'
 | 
						|
      )
 | 
						|
      await editor.expectState({
 | 
						|
        activeLines: [],
 | 
						|
        diagnostics: ['`badBadBadFn`isnotdefined'],
 | 
						|
        highlightedCode: '',
 | 
						|
      })
 | 
						|
      await expect(
 | 
						|
        page.getByText(
 | 
						|
          "Error in kcl script, sketch cannot be drawn until it's fixed"
 | 
						|
        )
 | 
						|
      ).toBeVisible()
 | 
						|
      // this checks sketch segments have failed to be drawn
 | 
						|
      await verifyArrowHeadColor(backgroundGray)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('', async () => {
 | 
						|
      await editor.replaceCode(
 | 
						|
        'badBadBadFn(endAbsolute = [pro',
 | 
						|
        'line(endAbsolute = [pro'
 | 
						|
      )
 | 
						|
      await editor.expectState({
 | 
						|
        activeLines: [],
 | 
						|
        diagnostics: [],
 | 
						|
        highlightedCode: '',
 | 
						|
      })
 | 
						|
      // this checks sketch segments have been drawn
 | 
						|
      await verifyArrowHeadColor(arrowHeadWhite)
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('make a change to the code and expect pixel color to change', async () => {
 | 
						|
      // defends against a regression where sketch would duplicate in the scene
 | 
						|
      // https://github.com/KittyCAD/modeling-app/issues/6345
 | 
						|
      await editor.replaceCode(
 | 
						|
        'startProfile(at = [75.8, 317.2',
 | 
						|
        'startProfile(at = [75.8, 217.2'
 | 
						|
      )
 | 
						|
      // expect not white anymore
 | 
						|
      await scene.expectPixelColorNotToBe(
 | 
						|
        TEST_COLORS.WHITE,
 | 
						|
        arrowHeadLocation,
 | 
						|
        15
 | 
						|
      )
 | 
						|
    })
 | 
						|
  })
 | 
						|
})
 |