import { test, expect, Page } from './zoo-test' import fs from 'node:fs/promises' import path from 'node:path' import { HomePageFixture } from './fixtures/homePageFixture' import { getMovementUtils, getUtils, PERSIST_MODELING_CONTEXT, TEST_COLORS, } from './test-utils' import { uuidv4, roundOff } from 'lib/utils' import { SceneFixture } from './fixtures/sceneFixture' test.describe('Sketch tests', { tag: ['@skipWin'] }, () => { test('multi-sketch file shows multiple Edit Sketch buttons', async ({ page, context, homePage, scene, }) => { const u = await getUtils(page) const selectionsSnippets = { startProfileAt1: '|> startProfileAt([-width / 4 + screwRadius, height / 2], %)', startProfileAt2: '|> startProfileAt([-width / 2, 0], %)', startProfileAt3: '|> startProfileAt([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({ radius = screwRadius, angleStart = 0, angleEnd = 360 }, %) part001 = startSketchOn('XY') ${startProfileAt2} |> xLine(width * .5, %) |> yLine(height, %) |> xLine(-width * .5, %) |> close() |> hole(screwHole, %) |> extrude(length = thickness) part002 = startSketchOn('-XZ') ${startProfileAt3} |> xLine(width / 4, %) |> tangentialArcTo([width / 2, 0], %) |> xLine(-width / 4 + wireRadius, %) |> yLine(wireOffset, %) |> arc({ radius = wireRadius, angleStart = 0, angleEnd = 180 }, %) |> yLine(-wireOffset, %) |> xLine(-width / 4, %) |> close() |> extrude(length = -height) ` ) }, selectionsSnippets ) await page.setBodyDimensions({ width: 1200, height: 500 }) await homePage.goToModelingScene() await scene.waitForExecutionDone() // 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, }) => { const u = await getUtils(page) await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') |> startProfileAt([2.61, -4.01], %) |> xLine(8.73, %) |> tangentialArcTo([8.33, -1.31], %)` ) }) await homePage.goToModelingScene() await scene.waitForExecutionDone() await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 587, y: 270 }, 15) await expect(async () => { await page.mouse.click(700, 200) await page.getByText('tangentialArcTo([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('tangentialArcTo([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(`sketch002 = startSketchOn('XZ') sketch001 = startProfileAt([12.34, -12.34], sketch002) |> yLine(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.describe('Can edit segments by dragging their handles', () => { const doEditSegmentsByDraggingHandle = async ( page: Page, homePage: HomePageFixture, openPanes: string[], scene: SceneFixture ) => { // Load the app with the code panes await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') |> startProfileAt([4.61, -14.01], %) |> line(end = [12.73, -0.09]) |> tangentialArcTo([24.95, -5.38], %) |> close()` ) }) const u = await getUtils(page) await homePage.goToModelingScene() await scene.waitForExecutionDone() 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') |> startProfileAt([4.61, -14.01], %) |> line(end = [12.73, -0.09]) |> tangentialArcTo([24.95, -5.38], %) |> 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('startProfileAt([4.61, -14.01], %)').click() } else { // Wait for the render. await page.waitForTimeout(1000) // Select the sketch await page.mouse.click(700, 370) } await expect( page.getByRole('button', { name: 'Edit Sketch' }) ).toBeVisible() await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.waitForTimeout(400) if (openPanes.includes('code')) { prevContent = await page.locator('.cm-content').innerText() } const step5 = { steps: 5 } await expect(page.getByTestId('segment-overlay')).toHaveCount(2) // 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 tangentialArcTo 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) } // Open the code pane await u.openKclCodePanel() // expect the code to have changed await expect(page.locator('.cm-content')) .toHaveText(`sketch001 = startSketchOn('XZ') |> startProfileAt([6.44, -12.07], %) |> line(end = [14.72, 1.97]) |> tangentialArcTo([24.95, -5.38], %) |> line(end = [1.97, 2.06]) |> close()`) } test( 'code pane open at start-handles', { tag: ['@skipWin'] }, async ({ page, homePage, scene }) => { // 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) } ) test( 'code pane closed at start-handles', { tag: ['@skipWin'] }, async ({ page, homePage, scene }) => { // 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) } ) }) test('Can edit a circle center and radius by dragging its handles', async ({ page, editor, homePage, }) => { const u = await getUtils(page) await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') |> circle({ center = [4.61, -5.01], radius = 8 }, %)` ) }) await homePage.goToModelingScene() 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, }) => { const u = await getUtils(page) await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') |> startProfileAt([4.61, -10.01], %) |> line(end = [12.73, -0.09]) |> tangentialArcTo([24.95, -0.38], %) |> close() |> extrude(length = 5)` ) }) await homePage.goToModelingScene() 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, 397] const dragPX = 40 await page.getByText('startProfileAt([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(2) // drag startProfieAt 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="0"]') 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 tangentialArcTo handle const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]') 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 expect(page.locator('.cm-content')) .toHaveText(`sketch001 = startSketchOn('XZ') |> startProfileAt([7.12, -12.68], %) |> line(end = [12.68, -1.09]) |> tangentialArcTo([24.89, 0.68], %) |> close() |> extrude(length = 5) `) }) test('Can edit a sketch that has been revolved in the same pipe', async ({ page, homePage, scene, }) => { const u = await getUtils(page) await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') |> startProfileAt([4.61, -14.01], %) |> line(end = [12.73, -0.09]) |> tangentialArcTo([24.95, -5.38], %) |> close() |> revolve({ axis = "X",}, %)` ) }) await homePage.goToModelingScene() await scene.waitForExecutionDone() 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('startProfileAt([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(2) // 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 tangentialArcTo 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 expect(page.locator('.cm-content')) .toHaveText(`sketch001 = startSketchOn('XZ') |> startProfileAt([6.44, -12.07], %) |> line(end = [14.72, 1.97]) |> tangentialArcTo([24.95, -5.38], %) |> line(end = [1.97, 2.06]) |> close() |> revolve({ axis = "X" }, %)`) }) 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 = "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 = startProfileAt(${toSU([0, 0])}, sketch001)` await expect(u.codeLocator).toHaveText(codeStr) await click00r(50, 0) await page.waitForTimeout(100) codeStr += ` |> xLine(${toU(50, 0)[0]}, %)` await expect(u.codeLocator).toHaveText(codeStr) await click00r(0, 50) codeStr += ` |> yLine(${toU(0, 50)[1]}, %)` await expect(u.codeLocator).toHaveText(codeStr) await click00r(-50, 0) codeStr += ` |> xLine(${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 = startProfileAt([2.03, 0], sketch002)` 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(2.04, %)` await expect(u.codeLocator).toHaveText(codeStr) await click00r(0, 30) codeStr += ` |> yLine(-2.03, %)` await expect(u.codeLocator).toHaveText(codeStr) await click00r(-30, 0) codeStr += ` |> xLine(-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 = `sketch001 = startSketchOn('-XZ') profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff( scale * 34.8 )}], sketch001) |> xLine(${roundOff(scale * 139.19)}, %) |> yLine(-${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( `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, }) => { // this was a regression https://github.com/KittyCAD/modeling-app/issues/2832 await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') |> startProfileAt([-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 "line(end = [1.32, 0.38])" await page.getByText(`line(end = [1.32, 0.38])`).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( page.getByRole('button', { name: 'Extrude' }) ).not.toBeDisabled() // click extrude await page.getByRole('button', { name: 'Extrude' }).click() // sketch selection should already have been made. "Selection: 1 face" only show up when the selection has been made already // otherwise the cmdbar would be waiting for a selection. await expect( page.getByRole('button', { name: 'selection : 1 segment', exact: false }) ).toBeVisible({ timeout: 10_000, }) }) 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') |> startProfileAt([-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') |> startProfileAt([-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, 'END') |> `.replace(/\s/g, '') ) }) // TODO: fix after electron migration is merged test.fixme( 'empty-scene default-planes act as expected', async ({ page, homePage }) => { /** * 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 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') |> startProfileAt([-10, -10], %) |> line(end = [20, 0]) |> line(end = [0, 20]) |> xLine(-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) await page.waitForTimeout(600) await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y) await page.waitForTimeout(200) await page.mouse.click(XYPlanePoint.x + 50, XYPlanePoint.y + 50) await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') |> startProfileAt([11.8, 9.09], %) |> line(end = [3.39, -3.39]) `) await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') |> startProfileAt([11.8, 9.09], %) |> line(end = [3.39, -3.39]) ` ) }) 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) |> startProfileAt([origin[0] + lugDiameter / 2, origin[1]], %) |> angledLineOfYLength({ angle = 60, length = lugHeadLength }, %) |> xLineTo(0 + .001, %) |> yLineTo(0, %) |> close() |> revolve({ axis = "Y" }, %) return lugSketch } lug([0, 0], 10, .5, "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('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 } const railTop = in2mm(.748) const railSide = in2mm(.024) const railBaseWidth = in2mm(.612) const railWideWidth = in2mm(.835) const railBaseLength = in2mm(.200) const railClampable = in2mm(.200) const rail = startSketchOn('XZ') |> startProfileAt([ -railTop / 2, railClampable + railBaseLength ], %) |> line(endAbsolute = [ railTop / 2, railClampable + railBaseLength ]) |> line(endAbsolute = [ railWideWidth / 2, railClampable / 2 + railBaseLength ], $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.describe('Sketch mode should be toleratant to syntax errors', () => { test( 'adding a syntax error, recovers after fixing', { tag: ['@skipWin'] }, async ({ page, homePage, context, scene, editor, toolbar }) => { const file = await fs.readFile( path.resolve( __dirname, '../../', './src/wasm-lib/tests/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) await test.step('check chamfer selection changes cursor positon', async () => { await expect(async () => { // sometimes initial click doesn't register await objClick() await editor.expectActiveLinesToBe([ '|> startProfileAt([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([pro') await editor.expectState({ activeLines: [], diagnostics: ['memoryitemkey`badBadBadFn`isnotdefined'], highlightedCode: '', }) // this checks sketch segments have failed to be drawn await verifyArrowHeadColor(backgroundGray) }) await test.step('', async () => { await editor.replaceCode('badBadBadFn([pro', 'line(endAbsolute = [pro') await editor.expectState({ activeLines: [], diagnostics: [], highlightedCode: '', }) // this checks sketch segments have been drawn await verifyArrowHeadColor(arrowHeadWhite) }) await page.waitForTimeout(100) } ) }) 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', `offsetPlane001 = offsetPlane("XY", 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: [`offsetPlane001=offsetPlane("XY",10)`], diagnostics: [], highlightedCode: 'offsetPlane("XY", 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: [`offsetPlane001=offsetPlane("XY",10)`], diagnostics: [], highlightedCode: '', }) }) }) }) }) test.describe('multi-profile sketching', () => { test(`snapToProfile start only works for current profile`, async ({ context, page, scene, toolbar, editor, homePage, }) => { // We seed the scene with a single offset plane await context.addInitScript(() => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') profile002 = startProfileAt([40.68, 87.67], sketch001) |> xLine(239.17, %) profile003 = startProfileAt([206.63, -56.73], sketch001) |> xLine(-156.32, %) ` ) }) await homePage.goToModelingScene() 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 = ` |> tangentialArcTo([39.49, 88.22], %)` await test.step('check that tangential tool does not snap to other profile starts', async () => { await toolbar.tangentialArcBtn.click() await endOfLowerSegMove() await endOfLowerSegClick() await profileStartOfHigherSegClick() 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( `tangentialArcTo([39.49, 88.22], %)` ) }) 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 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 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, and than back to the line tool', async () => { await startProfile1() await editor.expectEditor.toContain( `profile001 = startProfileAt([4.61, 12.21], sketch001)` ) await endLineStartTanArc() await editor.expectEditor.toContain(`|> line(end = [9.02, -0.55])`) await toolbar.tangentialArcBtn.click() await page.waitForTimeout(300) await page.mouse.click(745, 359) await page.waitForTimeout(300) await endLineStartTanArc({ delay: 544 }) await endArcStartLine() await editor.expectEditor.toContain( `|> tangentialArcTo([16.61, 4.14], %)` ) await toolbar.lineBtn.click() await page.waitForTimeout(300) await endArcStartLine() await page.mouse.click(572, 110) await editor.expectEditor.toContain(`|> line(end = [-11.73, 5.35])`) 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 = startProfileAt([19.12, 11.53], sketch001)` ) 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({ center = [23.19, 6.98], radius = 2.5 }, sketch001)` ) 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({ center = [23.74, 1.9], radius = 0.72 }, sketch001)` ) }) 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 = startProfileAt([5.63, 3.05], sketch001)` ) await crnRect1point2() await editor.expectEditor.toContain( `|> angledLine([0, 2.37], %, $rectangleSegmentA001) |> angledLine([segAng(rectangleSegmentA001) - 90, 7.8], %) |> angledLine([ segAng(rectangleSegmentA001), -segLen(rectangleSegmentA001) ], %) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close()`.replaceAll('\n', '') ) await crnRect2point1() await page.waitForTimeout(300) await editor.expectEditor.toContain( `profile006 = startProfileAt([11.05, 2.37], sketch001)` ) await crnRect2point2() await page.waitForTimeout(300) await editor.expectEditor.toContain( `|> angledLine([0, 5.49], %, $rectangleSegmentA002) |> angledLine([ segAng(rectangleSegmentA002) - 90, 4.14 ], %) |> angledLine([ segAng(rectangleSegmentA002), -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 = startProfileAt([8.41, -9.29], sketch001)` ) await cntrRect1point2() await page.waitForTimeout(300) await editor.expectEditor.toContain( `|> angledLine([0, 7.06], %, $rectangleSegmentA003) |> angledLine([ segAng(rectangleSegmentA003) + 90, 4.34 ], %) |> angledLine([ segAng(rectangleSegmentA003), -segLen(rectangleSegmentA003) ], %) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close()`.replaceAll('\n', '') ) await page.waitForTimeout(300) await cntrRect2point1() await page.waitForTimeout(300) await editor.expectEditor.toContain( `profile008 = startProfileAt([19.33, -5.56], sketch001)` ) await cntrRect2point2() await page.waitForTimeout(300) await editor.expectEditor.toContain( `|> angledLine([0, 3.12], %, $rectangleSegmentA004) |> angledLine([ segAng(rectangleSegmentA004) + 90, 6.24 ], %) |> angledLine([ segAng(rectangleSegmentA004), -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])' ) 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])' ) 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])' ) 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])' ) }) await test.step('double check that circle three point can be unequiped', async () => { // this was implicitly for other tools, but not for circle three point 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', `sketch001 = startSketchOn('XZ') profile001 = startProfileAt([6.24, 4.54], sketch001) |> line(end = [-0.41, 6.99]) |> line(end = [8.61, 0.74]) |> line(end = [10.99, -5.22]) profile002 = startProfileAt([11.19, 5.02], sketch001) |> angledLine([0, 10.78], %, $rectangleSegmentA001) |> angledLine([ segAng(rectangleSegmentA001) - 90, 4.14 ], %) |> angledLine([ segAng(rectangleSegmentA001), -segLen(rectangleSegmentA001) ], %) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() profile003 = circle({ center = [6.92, -4.2], radius = 3.16 }, sketch001) 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 pointOnSegment({ shouldDbClick: true }) await page.waitForTimeout(600) 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 editor.expectEditor.toContain( `angledLine([-7, 10.27], %, $rectangleSegmentA001)` ) }) await test.step('edit existing circl', async () => { await circleEdge() await page.mouse.down() await dragCircleTo() await page.mouse.up() await editor.expectEditor.toContain( `profile003 = circle({ center = [6.92, -4.2], radius = 4.81 }, sketch001)` ) }) await test.step('edit existing circle three point', async () => { await circ3PStart() await page.mouse.down() await circ3PEnd() await page.mouse.up() await editor.expectEditor.toContain( `profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07], p3 = [19.73, -1.33])` ) }) await test.step('add new profile', async () => { await toolbar.rectangleBtn.click() await page.waitForTimeout(100) await rectStart() await editor.expectEditor.toContain( `profile005 = startProfileAt([15.68, -3.84], sketch001)` ) await page.waitForTimeout(100) await rectEnd() await editor.expectEditor.toContain( `|> angledLine([180, 1.97], %, $rectangleSegmentA002) |> angledLine([ segAng(rectangleSegmentA002) + 90, 3.89 ], %) |> angledLine([ segAng(rectangleSegmentA002), -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', `sketch001 = startSketchOn('XZ') profile001 = startProfileAt([6.24, 4.54], sketch001) |> line(end = [-0.41, 6.99]) |> line(end = [8.61, 0.74]) |> line(end = [10.99, -5.22]) profile002 = startProfileAt([11.19, 5.02], sketch001) |> angledLine([0, 10.78], %, $rectangleSegmentA001) |> angledLine([ segAng(rectangleSegmentA001) - 90, 4.14 ], %) |> angledLine([ segAng(rectangleSegmentA001), -segLen(rectangleSegmentA001) ], %) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() profile003 = circle({ center = [6.92, -4.2], radius = 3.16 }, sketch001) ` ) }) 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 [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 toolbar.lineBtn.click() await page.waitForTimeout(100) }) 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') await page.waitForTimeout(600) }) 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: 5_000, intervals: [500] }) 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, }) => { await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') profile001 = startProfileAt([-63.43, 193.08], sketch001) |> line(end = [168.52, 149.87]) |> line(end = [190.29, -39.18]) |> tangentialArcTo([319.63, 129.65], %) |> line(end = [-217.65, -21.76]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() profile003 = startProfileAt([16.79, 38.24], sketch001) |> angledLine([0, 182.82], %, $rectangleSegmentA001) |> angledLine([ segAng(rectangleSegmentA001) - 90, 105.71 ], %) |> angledLine([ segAng(rectangleSegmentA001), -segLen(rectangleSegmentA001) ], %) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() profile004 = circle({ center = [280.45, 47.57], radius = 55.26 }, sketch001) extrude002 = extrude(profile001, length = 50) extrude001 = extrude(profile003, length = 5) ` ) }) await page.setBodyDimensions({ width: 1000, height: 500 }) await homePage.goToModelingScene() 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 scene.expectPixelColor([255, 255, 255], { x: 596, y: 165 }, 15) await scene.expectPixelColor([255, 255, 255], { x: 641, y: 220 }, 15) await scene.expectPixelColor([255, 255, 255], { 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', `myVar = 5`) }) await page.setBodyDimensions({ width: 1000, height: 500 }) await homePage.goToModelingScene() 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.exitSketchBtn.click() 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({ center = [12.41, 3.87], radius = myVar }, sketch001)` ) 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', `sketch001 = startSketchOn('XZ') profile001 = startProfileAt([85.19, 338.59], sketch001) |> line(end = [213.3, -94.52]) |> line(end = [-230.09, -55.34]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() sketch002 = startSketchOn('XY') profile002 = startProfileAt([85.81, 52.55], sketch002) ` ) }) 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('startProfileAt([85.81, 52.55], sketch002)').click() await toolbar.editSketch() // 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', `thePart = startSketchOn('XZ') |> startProfileAt([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') |> startProfileAt([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 = startProfileAt([7.53, 10.51], sketch001)` ) }) await test.step('can continue on to add a new profile to this sketch', async () => { await profilePoint1() await editor.expectEditor.toContain( `profile001 = startProfileAt([19.69, -7.05], sketch001)` ) 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, }) => { // TODO this test should include a test for selecting revolve walls and caps await page.addInitScript(async () => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') profile001 = startProfileAt([6.71, -3.66], sketch001) |> 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, seg01) profile002 = startProfileAt([0.75, 13.46], sketch002) |> line(end = [4.52, 3.79]) |> line(end = [5.98, -2.81]) profile003 = startProfileAt([3.19, 13.3], sketch002) |> angledLine([0, 6.64], %, $rectangleSegmentA001) |> angledLine([ segAng(rectangleSegmentA001) - 90, 2.81 ], %) |> angledLine([ segAng(rectangleSegmentA001), -segLen(rectangleSegmentA001) ], %) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() profile004 = startProfileAt([3.15, 9.39], sketch002) |> xLine(6.92, %) |> line(end = [-7.41, -2.85]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() profile005 = circle({ center = [5.15, 4.34], radius = 1.66 }, sketch002) profile006 = startProfileAt([9.65, 3.82], sketch002) |> line(end = [2.38, 5.62]) |> line(end = [2.13, -5.57]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() revolve001 = revolve({ angle = 45, axis = getNextAdjacentEdge(seg01) }, profile004) extrude002 = extrude(profile006, length = 4) sketch003 = startSketchOn('-XZ') profile007 = startProfileAt([4.8, 7.55], sketch003) |> line(end = [7.39, 2.58]) |> line(end = [7.02, -2.85]) profile008 = startProfileAt([5.54, 5.49], sketch003) |> line(end = [6.34, 2.64]) |> line(end = [6.33, -2.96]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() profile009 = startProfileAt([5.23, 1.95], sketch003) |> line(end = [6.8, 2.17]) |> line(end = [7.34, -2.75]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() profile010 = circle({ center = [7.18, -2.11], radius = 2.67 }, sketch003) profile011 = startProfileAt([5.07, -6.39], sketch003) |> angledLine([0, 4.54], %, $rectangleSegmentA002) |> angledLine([ segAng(rectangleSegmentA002) - 90, 4.17 ], %) |> angledLine([ segAng(rectangleSegmentA002), -segLen(rectangleSegmentA002) ], %) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() extrude003 = extrude(profile011, length = 2.5) // TODO this breaks the test, // revolve002 = revolve({ angle = 45, axis = seg02 }, profile008) ` ) }) await page.setBodyDimensions({ width: 1000, height: 500 }) await homePage.goToModelingScene() 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 camPositionForSelectingSketchOnCapProfiles = () => scene.moveCameraTo({ x: 404, y: 690, z: 38 }, { x: 16, y: -140, z: -10 }) 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 capSelectionOptions = [ { title: 'select cap segment', selectClick: scene.makeMouseHelpers(688, 91)[0], }, { title: 'select cap solid 2d', selectClick: scene.makeMouseHelpers(733, 204)[0], }, // TODO keeps failing // { // title: 'select cap circle', // selectClick: scene.makeMouseHelpers(679, 290)[0], // }, { title: 'select cap extrude wall', selectClick: scene.makeMouseHelpers(649, 402)[0], }, { title: 'select cap extrude cap', selectClick: scene.makeMouseHelpers(693, 408)[0], }, ] as const const verifyWallProfilesAreDrawn = async () => test.step('verify wall profiles are drawn', async () => { // open polygon await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 599, y: 168 }, 15) // closed polygon await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 656, y: 171 }, 15) // revolved profile await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 655, y: 264 }, 15) // extruded profile await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 808, y: 396 }, 15) // circle await scene.expectPixelColor( [ TEST_COLORS.WHITE, TEST_COLORS.BLUE, // When entering via the circle, it's selected and therefore blue ], { x: 742, y: 386 }, 15 ) }) const verifyCapProfilesAreDrawn = async () => test.step('verify wall profiles are drawn', async () => { // open polygon await scene.expectPixelColor( TEST_COLORS.WHITE, // TEST_COLORS.BLUE, // When entering via the circle, it's selected and therefore blue { x: 620, y: 58 }, 15 ) // revolved profile await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 641, y: 110 }, 15) // closed polygon await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 632, y: 200 }, 15) // extruded profile await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 628, y: 410 }, 15) // circle await scene.expectPixelColor( [ TEST_COLORS.WHITE, TEST_COLORS.BLUE, // When entering via the circle, it's selected and therefore blue ], { x: 681, y: 303 }, 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() await page.waitForTimeout(600) await verifyWallProfilesAreDrawn() await toolbar.exitSketchBtn.click() await page.waitForTimeout(100) }) } }) 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 page.waitForTimeout(100) 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', `sketch001 = startSketchOn('XZ') profile001 = startProfileAt([34, 42.66], sketch001) |> line(end = [102.65, 151.99]) |> line(end = [76, -138.66]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() plane001 = offsetPlane('XZ', 50) sketch002 = startSketchOn(plane001) profile002 = startProfileAt([39.43, 172.21], sketch002) |> xLine(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 = startProfileAt([50.72, -18.19], sketch001)` ) await rect1Crn2() await editor.expectEditor.toContain( `angledLine([0, 113.01], %, $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 = startProfileAt([34, 42.66], sketch001) |> line(end = [102.65, 151.99]) |> line(end = [76, -138.66]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() plane001 = offsetPlane('XZ', 50) sketch002 = startSketchOn(plane001) profile002 = startProfileAt([39.43, 172.21], sketch002) |> xLine(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 = startProfileAt([47.76, -17.13], plane001)` ) await rect1Crn2() await editor.expectEditor.toContain( `angledLine([0, 106.42], %, $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, }) => { // We seed the scene with a single offset plane await context.addInitScript(() => { localStorage.setItem( 'persistCode', `sketch001 = startSketchOn('XZ') |> startProfileAt([0, 0], %) |> line(end = [3.14, 3.14]) |> arcTo({ end = [4, 2], interior = [1, 2] }, %) ` ) }) await homePage.goToModelingScene() await scene.waitForExecutionDone() await test.step(`format the code`, async () => { // doesn't contain condensed version await editor.expectEditor.not.toContain( `arcTo({ end = [4, 2], interior = [1, 2] }, %)` ) // click the code to enter sketch mode await page.getByText(`arcTo`).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( `arcTo({ end = [4, 2], interior = [1, 2] }, %)` ) }) 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: 'arcTo({end = [4, 2], interior = [1, 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: 'arcTo({end = [4, 2], interior = [1, 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, }) => { // We seed the scene with a single offset plane await context.addInitScript(() => { localStorage.setItem( 'persistCode', ` sketch001 = startSketchOn('XZ') |> startProfileAt([256.85, 14.41], %) |> line(endAbsolute = [0, 211.07]) ` ) }) await homePage.goToModelingScene() await scene.waitForExecutionDone() 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() }) })