Split large (flow-tests) spec file into individual spec file per test suite (#3297)
* split flow tests spec file --------- Co-authored-by: ryanrosello-og <ry@zoo.dev>
This commit is contained in:
157
e2e/playwright/basic-sketch.spec.ts
Normal file
157
e2e/playwright/basic-sketch.spec.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import { test, expect, Page } from '@playwright/test'
|
||||||
|
import {
|
||||||
|
getUtils,
|
||||||
|
TEST_COLORS,
|
||||||
|
setup,
|
||||||
|
tearDown,
|
||||||
|
commonPoints,
|
||||||
|
PERSIST_MODELING_CONTEXT,
|
||||||
|
} from './test-utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.setTimeout(120000)
|
||||||
|
|
||||||
|
async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
// If we have the code pane open, we should see the code.
|
||||||
|
if (openPanes.includes('code')) {
|
||||||
|
await expect(u.codeLocator).toHaveText(``)
|
||||||
|
} else {
|
||||||
|
// Ensure we don't see the code.
|
||||||
|
await expect(u.codeLocator).not.toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// select a plane
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
if (openPanes.includes('code')) {
|
||||||
|
await expect(u.codeLocator).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XZ')`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
|
const startXPx = 600
|
||||||
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
if (openPanes.includes('code')) {
|
||||||
|
await expect(u.codeLocator)
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
|
} else {
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
if (openPanes.includes('code')) {
|
||||||
|
await expect(u.codeLocator)
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
} else {
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
|
if (openPanes.includes('code')) {
|
||||||
|
await expect(u.codeLocator)
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|
|> line([0, ${commonPoints.num1 + 0.01}], %)`)
|
||||||
|
} else {
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
}
|
||||||
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
|
if (openPanes.includes('code')) {
|
||||||
|
await expect(u.codeLocator)
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
||||||
|
|> line([-${commonPoints.num2}, 0], %)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deselect line tool
|
||||||
|
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
|
||||||
|
if (openPanes.includes('code')) {
|
||||||
|
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.WHITE)).toBeLessThan(3)
|
||||||
|
await expect(
|
||||||
|
await u.getGreatestPixDiff(line1, [249, 249, 249])
|
||||||
|
).toBeLessThan(3)
|
||||||
|
}
|
||||||
|
// click between first two clicks to get center of the line
|
||||||
|
await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
if (openPanes.includes('code')) {
|
||||||
|
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
|
||||||
|
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hold down shift
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
// click between the latest two clicks to get center of the line
|
||||||
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 20)
|
||||||
|
|
||||||
|
// selected two lines therefore there should be two cursors
|
||||||
|
if (openPanes.includes('code')) {
|
||||||
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Length: open menu' }).click()
|
||||||
|
await page.getByRole('button', { name: 'Equal Length' }).click()
|
||||||
|
|
||||||
|
// Open the code pane.
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await expect(u.codeLocator).toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %, $seg01)
|
||||||
|
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
||||||
|
|> angledLine([180, segLen(seg01)], %)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe('Basic sketch', () => {
|
||||||
|
test('code pane open at start', async ({ page }) => {
|
||||||
|
await doBasicSketch(page, ['code'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('code pane closed at start', async ({ page }) => {
|
||||||
|
// Load the app with the code panes
|
||||||
|
await page.addInitScript(async (persistModelingContext) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
persistModelingContext,
|
||||||
|
JSON.stringify({ openPanes: [] })
|
||||||
|
)
|
||||||
|
}, PERSIST_MODELING_CONTEXT)
|
||||||
|
await doBasicSketch(page, [])
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,111 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Can create sketches on all planes and their back sides', () => {
|
||||||
|
const sketchOnPlaneAndBackSideTest = async (
|
||||||
|
page: any,
|
||||||
|
plane: string,
|
||||||
|
clickCoords: { x: number; y: number }
|
||||||
|
) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
const coord =
|
||||||
|
plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100
|
||||||
|
const camCommand: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
center: { x: 0, y: 0, z: 0 },
|
||||||
|
vantage: { x: coord, y: coord, z: coord },
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const updateCamCommand: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = `const sketch001 = startSketchOn('${plane}')
|
||||||
|
|> startProfileAt([0.9, -1.22], %)`
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
|
await u.sendCustomCmd(camCommand)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.sendCustomCmd(updateCamCommand)
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.mouse.click(clickCoords.x, clickCoords.y)
|
||||||
|
await page.waitForTimeout(300) // wait for animation
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Line', exact: true })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// draw a line
|
||||||
|
const startXPx = 600
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.removeCurrentCode()
|
||||||
|
}
|
||||||
|
test('XY', async ({ page }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(
|
||||||
|
page,
|
||||||
|
'XY',
|
||||||
|
{ x: 600, y: 388 } // red plane
|
||||||
|
// { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('YZ', async ({ page }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, 'YZ', { x: 700, y: 250 }) // green plane
|
||||||
|
})
|
||||||
|
|
||||||
|
test('XZ', async ({ page }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 80 }) // blue plane
|
||||||
|
})
|
||||||
|
|
||||||
|
test('-XY', async ({ page }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, '-XY', { x: 600, y: 118 }) // back of red plane
|
||||||
|
})
|
||||||
|
|
||||||
|
test('-YZ', async ({ page }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, '-YZ', { x: 700, y: 219 }) // back of green plane
|
||||||
|
})
|
||||||
|
|
||||||
|
test('-XZ', async ({ page }) => {
|
||||||
|
await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 427 }) // back of blue plane
|
||||||
|
})
|
||||||
|
})
|
219
e2e/playwright/code-pane-and-errors.spec.ts
Normal file
219
e2e/playwright/code-pane-and-errors.spec.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Code pane and errors', () => {
|
||||||
|
test('Typing KCL errors induces a badge on the code pane button', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Load the app with the working starter code
|
||||||
|
await page.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, bracket)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// Ensure no badge is present
|
||||||
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
|
await expect(codePaneButtonHolder).not.toContainText('notification')
|
||||||
|
|
||||||
|
// Delete a character to break the KCL
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await page.getByText('extrude(').click()
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
|
// Ensure that a badge appears on the button
|
||||||
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Opening and closing the code pane will consistently show error diagnostics', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Load the app with the working starter code
|
||||||
|
await page.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, bracket)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 900 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// Ensure we have no errors in the gutter.
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
// Ensure no badge is present
|
||||||
|
const codePaneButton = page.getByRole('button', { name: 'KCL Code pane' })
|
||||||
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
|
await expect(codePaneButtonHolder).not.toContainText('notification')
|
||||||
|
|
||||||
|
// Delete a character to break the KCL
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await page.getByText('extrude(').click()
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
|
// Ensure that a badge appears on the button
|
||||||
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
|
|
||||||
|
// Ensure we have an error diagnostic.
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||||
|
|
||||||
|
// Close the code pane
|
||||||
|
await codePaneButton.click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
// Ensure that a badge appears on the button
|
||||||
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
|
// Ensure we have no errors in the gutter.
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
// Open the code pane
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
|
||||||
|
// Ensure that a badge appears on the button
|
||||||
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
|
|
||||||
|
// Ensure we have an error diagnostic.
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('When error is not in view you can click the badge to scroll to it', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Load the app with the working starter code
|
||||||
|
await page.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
// Ensure badge is present
|
||||||
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
|
|
||||||
|
// Ensure we have no errors in the gutter, since error out of view.
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
// Click the badge.
|
||||||
|
const badge = page.locator('#code-badge')
|
||||||
|
await expect(badge).toBeVisible()
|
||||||
|
await badge.click()
|
||||||
|
|
||||||
|
// Ensure we have an error diagnostic.
|
||||||
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
|
|
||||||
|
// Hover over the error to see the error message
|
||||||
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByText(
|
||||||
|
'sketch profile must lie entirely on one side of the revolution axis'
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Load the app with the working starter code
|
||||||
|
await page.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
// Ensure badge is present
|
||||||
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
|
|
||||||
|
// Ensure we have no errors in the gutter, since error out of view.
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
// click in the editor to focus it
|
||||||
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
// go to the start of the editor and enter more text which will trigger
|
||||||
|
// a lint error.
|
||||||
|
// GO to the start of the editor.
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('Home')
|
||||||
|
await page.keyboard.type('const foo_bar = 1')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// ensure we have a lint error
|
||||||
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
|
|
||||||
|
// Click the badge.
|
||||||
|
const badge = page.locator('#code-badge')
|
||||||
|
await expect(badge).toBeVisible()
|
||||||
|
await badge.click()
|
||||||
|
|
||||||
|
// Ensure we have an error diagnostic.
|
||||||
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
|
|
||||||
|
// Hover over the error to see the error message
|
||||||
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByText(
|
||||||
|
'sketch profile must lie entirely on one side of the revolution axis'
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
363
e2e/playwright/command-bar-tests.spec.ts
Normal file
363
e2e/playwright/command-bar-tests.spec.ts
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Command bar tests', () => {
|
||||||
|
test('Extrude from command bar selects extrude line after', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> xLine(-20, %)
|
||||||
|
|> close(%)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// Click the line of code for xLine.
|
||||||
|
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||||
|
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Fillet from command bar', async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-5, -5], %)
|
||||||
|
|> line([0, 10], %)
|
||||||
|
|> line([10, 0], %)
|
||||||
|
|> line([0, -10], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(-10, sketch001)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
const selectSegment = () => page.getByText(`line([0, -10], %)`).click()
|
||||||
|
|
||||||
|
await selectSegment()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.getByRole('button', { name: 'Fillet' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-activeLine')).toContainText(
|
||||||
|
`fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
|
const themeOption = page.getByRole('option', {
|
||||||
|
name: 'theme',
|
||||||
|
exact: false,
|
||||||
|
})
|
||||||
|
const commandLevelArgButton = page.getByRole('button', { name: 'level' })
|
||||||
|
const commandThemeArgButton = page.getByRole('button', { name: 'value' })
|
||||||
|
// This selector changes after we set the setting
|
||||||
|
let commandOptionInput = page.getByPlaceholder('Select an option')
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
|
||||||
|
// First try opening the command bar and closing it
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Commands', exact: false })
|
||||||
|
.or(page.getByRole('button', { name: '⌘K' }))
|
||||||
|
.click()
|
||||||
|
|
||||||
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await expect(cmdSearchBar).not.toBeVisible()
|
||||||
|
|
||||||
|
// Now try the same, but with the keyboard shortcut, check focus
|
||||||
|
await page.keyboard.press('Meta+K')
|
||||||
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
await expect(cmdSearchBar).toBeFocused()
|
||||||
|
|
||||||
|
// Try typing in the command bar
|
||||||
|
await cmdSearchBar.fill('theme')
|
||||||
|
await expect(themeOption).toBeVisible()
|
||||||
|
await themeOption.click()
|
||||||
|
const themeInput = page.getByPlaceholder('Select an option')
|
||||||
|
await expect(themeInput).toBeVisible()
|
||||||
|
await expect(themeInput).toBeFocused()
|
||||||
|
// Select dark theme
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||||
|
'data-headlessui-state',
|
||||||
|
'active'
|
||||||
|
)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// Check the toast appeared
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Set theme to "system" for this project`)
|
||||||
|
).toBeVisible()
|
||||||
|
// Check that the theme changed
|
||||||
|
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||||
|
|
||||||
|
commandOptionInput = page.getByPlaceholder('system')
|
||||||
|
|
||||||
|
// Test case for https://github.com/KittyCAD/modeling-app/issues/2882
|
||||||
|
await commandBarButton.click()
|
||||||
|
await cmdSearchBar.focus()
|
||||||
|
await cmdSearchBar.fill('theme')
|
||||||
|
await themeOption.click()
|
||||||
|
await expect(commandThemeArgButton).toBeDisabled()
|
||||||
|
await commandOptionInput.focus()
|
||||||
|
await commandOptionInput.fill('lig')
|
||||||
|
await commandLevelArgButton.click()
|
||||||
|
await expect(commandLevelArgButton).toBeDisabled()
|
||||||
|
|
||||||
|
// Test case for https://github.com/KittyCAD/modeling-app/issues/2881
|
||||||
|
await commandThemeArgButton.click()
|
||||||
|
await expect(commandThemeArgButton).toBeDisabled()
|
||||||
|
await expect(commandLevelArgButton).toHaveText('level: project')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
|
||||||
|
// Put the cursor in the code editor
|
||||||
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
|
// Now try the same, but with the keyboard shortcut, check focus
|
||||||
|
await page.keyboard.press('Meta+K')
|
||||||
|
|
||||||
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
await expect(cmdSearchBar).toBeFocused()
|
||||||
|
|
||||||
|
// Try typing in the command bar
|
||||||
|
await cmdSearchBar.fill('theme')
|
||||||
|
const themeOption = page.getByRole('option', {
|
||||||
|
name: 'Settings · app · theme',
|
||||||
|
})
|
||||||
|
await expect(themeOption).toBeVisible()
|
||||||
|
await themeOption.click()
|
||||||
|
const themeInput = page.getByPlaceholder('Select an option')
|
||||||
|
await expect(themeInput).toBeVisible()
|
||||||
|
await expect(themeInput).toBeFocused()
|
||||||
|
// Select dark theme
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||||
|
'data-headlessui-state',
|
||||||
|
'active'
|
||||||
|
)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// Check the toast appeared
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Set theme to "system" for this project`)
|
||||||
|
).toBeVisible()
|
||||||
|
// Check that the theme changed
|
||||||
|
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Can extrude from the command bar', async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const distance = sqrt(20)
|
||||||
|
const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-6.95, 10.98], %)
|
||||||
|
|> line([25.1, 0.41], %)
|
||||||
|
|> line([0.73, -20.93], %)
|
||||||
|
|> line([-23.44, 0.52], %)
|
||||||
|
|> close(%)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// Make sure the stream is up
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
|
||||||
|
|
||||||
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
|
await page.keyboard.press('Meta+K')
|
||||||
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
|
// Search for extrude command and choose it
|
||||||
|
await page.getByRole('option', { name: 'Extrude' }).click()
|
||||||
|
|
||||||
|
// Assert that we're on the selection step
|
||||||
|
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
|
||||||
|
// Select a face
|
||||||
|
await page.mouse.move(700, 200)
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
// Assert that we're on the distance step
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'distance', exact: false })
|
||||||
|
).toBeDisabled()
|
||||||
|
|
||||||
|
// Assert that the an alternative variable name is chosen,
|
||||||
|
// since the default variable name is already in use (distance)
|
||||||
|
await page.getByRole('button', { name: 'Create new variable' }).click()
|
||||||
|
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
|
||||||
|
'distance001'
|
||||||
|
)
|
||||||
|
|
||||||
|
const continueButton = page.getByRole('button', { name: 'Continue' })
|
||||||
|
const submitButton = page.getByRole('button', { name: 'Submit command' })
|
||||||
|
await continueButton.click()
|
||||||
|
|
||||||
|
// Review step and argument hotkeys
|
||||||
|
await expect(submitButton).toBeEnabled()
|
||||||
|
await expect(submitButton).toBeFocused()
|
||||||
|
await submitButton.press('Backspace')
|
||||||
|
|
||||||
|
// Assert we're back on the distance step
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'distance', exact: false })
|
||||||
|
).toBeDisabled()
|
||||||
|
|
||||||
|
await continueButton.click()
|
||||||
|
await submitButton.click()
|
||||||
|
|
||||||
|
// Check that the code was updated
|
||||||
|
await u.waitForCmdReceive('extrude')
|
||||||
|
// Unfortunately this indentation seems to matter for the test
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const distance = sqrt(20)
|
||||||
|
const distance001 = ${KCL_DEFAULT_LENGTH}
|
||||||
|
const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-6.95, 10.98], %)
|
||||||
|
|> line([25.1, 0.41], %)
|
||||||
|
|> line([0.73, -20.93], %)
|
||||||
|
|> line([-23.44, 0.52], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(distance001, sketch001)`.replace(
|
||||||
|
/(\r\n|\n|\r)/gm,
|
||||||
|
''
|
||||||
|
) // remove newlines
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Can switch between sketch tools via command bar', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
|
const rectangleToolCommand = page.getByRole('option', {
|
||||||
|
name: 'rectangle',
|
||||||
|
})
|
||||||
|
const rectangleToolButton = page.getByRole('button', {
|
||||||
|
name: 'Corner rectangle',
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
const lineToolCommand = page.getByRole('option', {
|
||||||
|
name: 'Line',
|
||||||
|
})
|
||||||
|
const lineToolButton = page.getByRole('button', {
|
||||||
|
name: 'Line',
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
const arcToolCommand = page.getByRole('option', { name: 'Tangential Arc' })
|
||||||
|
const arcToolButton = page.getByRole('button', {
|
||||||
|
name: 'Tangential Arc',
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start a sketch
|
||||||
|
await sketchButton.click()
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
// Switch between sketch tools via the command bar
|
||||||
|
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
await cmdBarButton.click()
|
||||||
|
await rectangleToolCommand.click()
|
||||||
|
await expect(rectangleToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
await cmdBarButton.click()
|
||||||
|
await lineToolCommand.click()
|
||||||
|
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
|
||||||
|
// Click in the scene a couple times to draw a line
|
||||||
|
// so tangential arc is valid
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
await page.mouse.move(700, 300, { steps: 5 })
|
||||||
|
await page.mouse.click(700, 300)
|
||||||
|
|
||||||
|
// switch to tangential arc via command bar
|
||||||
|
await cmdBarButton.click()
|
||||||
|
await arcToolCommand.click()
|
||||||
|
await expect(arcToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
})
|
||||||
|
})
|
519
e2e/playwright/copilot-ghost-test.spec.ts
Normal file
519
e2e/playwright/copilot-ghost-test.spec.ts
Normal file
@ -0,0 +1,519 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
test.describe('Copilot ghost text', () => {
|
||||||
|
test.skip(true, 'Needs to get covered again')
|
||||||
|
|
||||||
|
test('completes code in empty file', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// We should be able to hit Tab to accept the completion.
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hit enter a few times.
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %) `
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test.skip('copilot disabled in sketch mode no select plane', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
// Click sketch mode.
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
// Exit sketch mode.
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// We should be able to hit Tab to accept the completion.
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('copilot disabled in sketch mode after selecting plane', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
// Click sketch mode.
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
|
// select a plane
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
await page.waitForTimeout(700) // wait for animation
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XZ')`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Escape to exit the tool.
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XZ')`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Escape again to exit sketch mode.
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XZ')fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// We should be able to hit Tab to accept the completion.
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XZ')fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hit enter a few times.
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XZ')fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %) `
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ArrowUp in code rejects the suggestion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ArrowDown in code rejects the suggestion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ArrowLeft in code rejects the suggestion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
|
await page.keyboard.press('ArrowLeft')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ArrowRight in code rejects the suggestion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
|
await page.keyboard.press('ArrowRight')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Enter in code scoots it down', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Ctrl+shift+z in code rejects the suggestion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
|
await page.keyboard.down(CtrlKey)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up(CtrlKey)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Ctrl+z in code rejects the suggestion and undos the last code', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||||
|
|
||||||
|
await page.waitForTimeout(800)
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await page.keyboard.type('{thing: "blah"}', { delay: 0 })
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`)
|
||||||
|
|
||||||
|
// We wanna make sure the code saves.
|
||||||
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
|
// Ctrl+z
|
||||||
|
await page.keyboard.down(CtrlKey)
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up(CtrlKey)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
// Ctrl+shift+z
|
||||||
|
await page.keyboard.down(CtrlKey)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up(CtrlKey)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`)
|
||||||
|
|
||||||
|
// We wanna make sure the code saves.
|
||||||
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`{thing: "blah"}fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Once for the enter.
|
||||||
|
await page.keyboard.down(CtrlKey)
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up(CtrlKey)
|
||||||
|
|
||||||
|
// Once for the text.
|
||||||
|
await page.keyboard.down(CtrlKey)
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up(CtrlKey)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
// TODO when we make codemirror a widget, we can test this.
|
||||||
|
//await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('delete in code rejects the suggestion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
|
await page.keyboard.press('Delete')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('backspace in code rejects the suggestion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('focus outside code pane rejects the suggestion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
|
`fn cube = (pos, scale) => {`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Going outside the editor should hide the ghost text.
|
||||||
|
await page.mouse.move(0, 0)
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.waitFor({ state: 'visible' })
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
|
})
|
867
e2e/playwright/editor-tests.spec.ts
Normal file
867
e2e/playwright/editor-tests.spec.ts
Normal file
@ -0,0 +1,867 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Editor tests', () => {
|
||||||
|
test('can comment out code with ctrl+/', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type(`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
await page.keyboard.down(CtrlKey)
|
||||||
|
await page.keyboard.press('/')
|
||||||
|
await page.keyboard.up(CtrlKey)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
// |> close(%)`)
|
||||||
|
|
||||||
|
// uncomment the code
|
||||||
|
await page.keyboard.down(CtrlKey)
|
||||||
|
await page.keyboard.press('/')
|
||||||
|
await page.keyboard.up(CtrlKey)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if you click the format button it formats your code', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type(`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
await page.locator('#code-pane button:first-child').click()
|
||||||
|
await page.locator('button:has-text("Format code")').click()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('fold gutters work', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
const fullCode = `const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// TODO: Jess needs to fix this but you have to mod the code to get them to show
|
||||||
|
// up, its an annoying codemirror thing.
|
||||||
|
await page.locator('.cm-content').click()
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
const foldGutterFoldLine = page.locator('[title="Fold line"]')
|
||||||
|
const foldGutterUnfoldLine = page.locator('[title="Unfold line"]')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(fullCode)
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
// Make sure we have a fold gutter
|
||||||
|
await expect(foldGutterFoldLine).toBeVisible()
|
||||||
|
await expect(foldGutterUnfoldLine).not.toBeVisible()
|
||||||
|
|
||||||
|
// Collapse the code
|
||||||
|
await foldGutterFoldLine.click()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XY')… `
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(fullCode)
|
||||||
|
await expect(foldGutterFoldLine).not.toBeVisible()
|
||||||
|
await expect(foldGutterUnfoldLine.nth(1)).toBeVisible()
|
||||||
|
|
||||||
|
// Expand the code
|
||||||
|
await foldGutterUnfoldLine.nth(1).click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(fullCode)
|
||||||
|
|
||||||
|
// Delete all the code.
|
||||||
|
await page.locator('.cm-content').click()
|
||||||
|
// Select all
|
||||||
|
await page.keyboard.press('Control+A')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.keyboard.press('Meta+A')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(fullCode)
|
||||||
|
|
||||||
|
await expect(foldGutterUnfoldLine).not.toBeVisible()
|
||||||
|
await expect(foldGutterFoldLine).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hover over functions shows function description', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// focus the editor
|
||||||
|
await u.codeLocator.click()
|
||||||
|
|
||||||
|
// Hover over the startSketchOn function
|
||||||
|
await page.getByText('startSketchOn').hover()
|
||||||
|
await expect(page.locator('.hover-tooltip')).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByText(
|
||||||
|
'Start a new 2-dimensional sketch on a specific plane or face'
|
||||||
|
)
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// Hover over the line function
|
||||||
|
await page.getByText('line').first().hover()
|
||||||
|
await expect(page.locator('.hover-tooltip')).toBeVisible()
|
||||||
|
await expect(page.getByText('Draw a line')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if you use the format keyboard binding it formats your code', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
localStorage.setItem('disableAxis', 'true')
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// focus the editor
|
||||||
|
await u.codeLocator.click()
|
||||||
|
|
||||||
|
// Hit alt+shift+f to format the code
|
||||||
|
await page.keyboard.press('Alt+Shift+KeyF')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if you write kcl with lint errors you get lints', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type('const my_snake_case_var = 5')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type('const myCamelCaseVar = 5')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// press arrows to clear autocomplete
|
||||||
|
await page.keyboard.press('ArrowLeft')
|
||||||
|
await page.keyboard.press('ArrowRight')
|
||||||
|
|
||||||
|
// error in guter
|
||||||
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-info')
|
||||||
|
await expect(
|
||||||
|
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// select the line that's causing the error and delete it
|
||||||
|
await page.getByText('const my_snake_case_var = 5').click()
|
||||||
|
await page.keyboard.press('End')
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.keyboard.press('Home')
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
|
// wait for .cm-lint-marker-info not to be visible
|
||||||
|
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if you fixup kcl errors you clear lints', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([3.29, 7.86], %)
|
||||||
|
|> line([2.48, 2.44], %)
|
||||||
|
|> line([2.66, 1.17], %)
|
||||||
|
|> close(%)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
|
||||||
|
await page.getByText(' |> line([2.48, 2.44], %)').click()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.locator('.cm-lint-marker-error').first()
|
||||||
|
).not.toBeVisible()
|
||||||
|
await page.keyboard.press('End')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
|
await page.keyboard.type(')')
|
||||||
|
await expect(
|
||||||
|
page.locator('.cm-lint-marker-error').first()
|
||||||
|
).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
/* add the following code to the editor ($ error is not a valid line)
|
||||||
|
$ error
|
||||||
|
const topAng = 30
|
||||||
|
const bottomAng = 25
|
||||||
|
*/
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type('$ error')
|
||||||
|
|
||||||
|
// press arrows to clear autocomplete
|
||||||
|
await page.keyboard.press('ArrowLeft')
|
||||||
|
await page.keyboard.press('ArrowRight')
|
||||||
|
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type('const topAng = 30')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type('const bottomAng = 25')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// error in guter
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||||
|
|
||||||
|
// select the line that's causing the error and delete it
|
||||||
|
await page.getByText('$ error').click()
|
||||||
|
await page.keyboard.press('End')
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.keyboard.press('Home')
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
|
// wait for .cm-lint-marker-error not to be visible
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
// let's check we get an error when defining the same variable twice
|
||||||
|
await page.getByText('const bottomAng = 25').click()
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type("// Let's define the same thing twice")
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type('const topAng = 42')
|
||||||
|
await page.keyboard.press('ArrowLeft')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.locator('.cm-lint-marker.cm-lint-marker-error')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
await page.locator('.cm-lint-marker.cm-lint-marker-error').hover()
|
||||||
|
await expect(page.locator('.cm-diagnosticText').first()).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByText('Cannot redefine `topAng`').first()
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
const secondTopAng = page.getByText('topAng').first()
|
||||||
|
await secondTopAng?.dblclick()
|
||||||
|
await page.keyboard.type('otherAng')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const length = .750
|
||||||
|
const width = 0.500
|
||||||
|
const height = 0.500
|
||||||
|
const dia = 4
|
||||||
|
|
||||||
|
fn squareHole = (l, w) => {
|
||||||
|
const squareHoleSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-width / 2, -length / 2], %)
|
||||||
|
|> lineTo([width / 2, -length / 2], %)
|
||||||
|
|> lineTo([width / 2, length / 2], %)
|
||||||
|
|> lineTo([-width / 2, length / 2], %)
|
||||||
|
|> close(%)
|
||||||
|
return squareHoleSketch
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
// Click on the bottom of the code editor to add a new line
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type(`const extrusion = startSketchOn('XY')
|
||||||
|
|> circle([0, 0], dia/2, %)
|
||||||
|
|> hole(squareHole(length, width, height), %)
|
||||||
|
|> extrude(height, %)`)
|
||||||
|
|
||||||
|
// error in gutter
|
||||||
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
|
await page.hover('.cm-lint-marker-error:first-child')
|
||||||
|
await expect(
|
||||||
|
page.getByText('Expected 2 arguments, got 3').first()
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// Make sure there are two diagnostics
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
||||||
|
})
|
||||||
|
test('if your kcl gets an error from the engine it is inlined', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const box = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([0, 10], %)
|
||||||
|
|> line([10, 0], %)
|
||||||
|
|> line([0, -10], %, $revolveAxis)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(10, %)
|
||||||
|
|
||||||
|
const sketch001 = startSketchOn(box, revolveAxis)
|
||||||
|
|> startProfileAt([5, 10], %)
|
||||||
|
|> line([0, -10], %)
|
||||||
|
|> line([2, 0], %)
|
||||||
|
|> line([0, -10], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({
|
||||||
|
axis: revolveAxis,
|
||||||
|
angle: 90
|
||||||
|
}, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
const searchText =
|
||||||
|
'sketch profile must lie entirely on one side of the revolution axis'
|
||||||
|
await expect(page.getByText(searchText)).toBeVisible()
|
||||||
|
})
|
||||||
|
test.describe('Autocomplete works', () => {
|
||||||
|
test('with enter/click to accept the completion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// this test might be brittle as we add and remove functions
|
||||||
|
// but should also be easy to update.
|
||||||
|
// tests clicking on an option, selection the first option
|
||||||
|
// and arrowing down to an option
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type('const sketch001 = start')
|
||||||
|
|
||||||
|
// expect there to be six auto complete options
|
||||||
|
await expect(page.locator('.cm-completionLabel')).toHaveCount(8)
|
||||||
|
// this makes sure we can accept a completion with click
|
||||||
|
await page.getByText('startSketchOn').click()
|
||||||
|
await page.keyboard.type("'XZ'")
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type(' |> startProfi')
|
||||||
|
// expect there be a single auto complete option that we can just hit enter on
|
||||||
|
await expect(page.locator('.cm-completionLabel')).toBeVisible()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter') // accepting the auto complete, not a new line
|
||||||
|
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.type('12')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.type(' |> lin')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// press arrow down twice then enter to accept xLine
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
// finish line with comment
|
||||||
|
await page.keyboard.type('5')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
|
||||||
|
await page.keyboard.type(' // ')
|
||||||
|
// Since we need to parse the ast to know we are in a comment we gotta hang tight.
|
||||||
|
await page.waitForTimeout(700)
|
||||||
|
await page.keyboard.type('lin ')
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// there shouldn't be any auto complete options for 'lin' in the comment
|
||||||
|
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([3.14, 12], %)
|
||||||
|
|> xLine(5, %) // lin`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('with tab to accept the completion', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// this test might be brittle as we add and remove functions
|
||||||
|
// but should also be easy to update.
|
||||||
|
// tests clicking on an option, selection the first option
|
||||||
|
// and arrowing down to an option
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type('const sketch001 = startSketchO')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// Make sure just hitting tab will take the only one left
|
||||||
|
await expect(page.locator('.cm-completionLabel')).toHaveCount(1)
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.type("'XZ'")
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type(' |> startProfi')
|
||||||
|
// expect there be a single auto complete option that we can just hit enter on
|
||||||
|
await expect(page.locator('.cm-completionLabel')).toBeVisible()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab') // accepting the auto complete, not a new line
|
||||||
|
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.keyboard.type('12')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.type(' |> lin')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// press arrow down twice then tab to accept xLine
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
// finish line with comment
|
||||||
|
await page.keyboard.type('5')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Tab')
|
||||||
|
|
||||||
|
await page.keyboard.type(' // ')
|
||||||
|
// Since we need to parse the ast to know we are in a comment we gotta hang tight.
|
||||||
|
await page.waitForTimeout(700)
|
||||||
|
await page.keyboard.type('lin ')
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// there shouldn't be any auto complete options for 'lin' in the comment
|
||||||
|
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([3.14, 12], %)
|
||||||
|
|> xLine(5, %) // lin`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test('Can undo a click and point extrude with ctrl+z', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([12.73, -0.09], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
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 = 40
|
||||||
|
|
||||||
|
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||||
|
|
||||||
|
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// expect the code to have changed
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XZ') |> startProfileAt([4.61, -14.01], %) |> line([12.73, -0.09], %) |> tangentialArcTo([24.95, -5.38], %) |> close(%)const extrude001 = extrude(5, sketch001)`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Now hit undo
|
||||||
|
await page.keyboard.down('Control')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([12.73, -0.09], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// failing for the same reason as "Can edit a sketch that has been extruded in the same pipe"
|
||||||
|
// please fix together
|
||||||
|
test.fixme('Can undo a sketch modification with ctrl+z', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([12.73, -0.09], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
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 = 40
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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
|
||||||
|
// we wait so it saves the code
|
||||||
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
|
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, y: lineEnd.y + dragPX },
|
||||||
|
})
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||||
|
prevContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
// we wait so it saves the code
|
||||||
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
|
// drag tangentialArcTo handle
|
||||||
|
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||||
|
await page.dragAndDrop('#stream', '#stream', {
|
||||||
|
sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 },
|
||||||
|
targetPosition: {
|
||||||
|
x: tangentEnd.x + dragPX,
|
||||||
|
y: tangentEnd.y + dragPX,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
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(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([7.12, -16.82], %)
|
||||||
|
|> line([15.4, -2.74], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> line([2.65, -2.69], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)`)
|
||||||
|
|
||||||
|
// Hit undo
|
||||||
|
await page.keyboard.down('Control')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([7.12, -16.82], %)
|
||||||
|
|> line([15.4, -2.74], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)`)
|
||||||
|
|
||||||
|
// Hit undo again.
|
||||||
|
await page.keyboard.down('Control')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([7.12, -16.82], %)
|
||||||
|
|> line([12.73, -0.09], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)`)
|
||||||
|
|
||||||
|
// Hit undo again.
|
||||||
|
await page.keyboard.down('Control')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([12.73, -0.09], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)`)
|
||||||
|
})
|
||||||
|
})
|
File diff suppressed because it is too large
Load Diff
341
e2e/playwright/onboarding-tests.spec.ts
Normal file
341
e2e/playwright/onboarding-tests.spec.ts
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
|
import {
|
||||||
|
TEST_SETTINGS_KEY,
|
||||||
|
TEST_SETTINGS_ONBOARDING_START,
|
||||||
|
TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||||
|
TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING,
|
||||||
|
TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||||
|
} from './storageStates'
|
||||||
|
import * as TOML from '@iarna/toml'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Onboarding tests', () => {
|
||||||
|
test('Onboarding code is shown in the editor', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ settingsKey }) => {
|
||||||
|
// Give no initial code, so that the onboarding start is shown immediately
|
||||||
|
localStorage.removeItem('persistCode')
|
||||||
|
localStorage.removeItem(settingsKey)
|
||||||
|
},
|
||||||
|
{ settingsKey: TEST_SETTINGS_KEY }
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// Test that the onboarding pane loaded
|
||||||
|
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||||
|
|
||||||
|
// *and* that the code is shown in the editor
|
||||||
|
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Code resets after confirmation', async ({ page }) => {
|
||||||
|
const initialCode = `const sketch001 = startSketchOn('XZ')`
|
||||||
|
|
||||||
|
// Load the page up with some code so we see the confirmation warning
|
||||||
|
// when we go to replay onboarding
|
||||||
|
await page.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, initialCode)
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// Replay the onboarding
|
||||||
|
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||||
|
const replayButton = page.getByRole('button', { name: 'Replay onboarding' })
|
||||||
|
await expect(replayButton).toBeVisible()
|
||||||
|
await replayButton.click()
|
||||||
|
|
||||||
|
// Ensure we see the warning, and that the code has not yet updated
|
||||||
|
await expect(
|
||||||
|
page.getByText('Replaying onboarding resets your code')
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(initialCode)
|
||||||
|
|
||||||
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
|
await expect(nextButton).toBeVisible()
|
||||||
|
await nextButton.click()
|
||||||
|
|
||||||
|
// Ensure we see the introduction and that the code has been reset
|
||||||
|
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||||
|
|
||||||
|
// Ensure we persisted the code to local storage.
|
||||||
|
// Playwright's addInitScript method unfortunately will reset
|
||||||
|
// this code if we try reloading the page as a test,
|
||||||
|
// so this is our best way to test persistence afaik.
|
||||||
|
expect(
|
||||||
|
await page.evaluate(() => {
|
||||||
|
return localStorage.getItem('persistCode')
|
||||||
|
})
|
||||||
|
).toContain('// Shelf Bracket')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Click through each onboarding step', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
// Give no initial code, so that the onboarding start is shown immediately
|
||||||
|
localStorage.setItem('persistCode', '')
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// Test that the onboarding pane loaded
|
||||||
|
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||||
|
|
||||||
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
|
|
||||||
|
while ((await nextButton.innerText()) !== 'Finish') {
|
||||||
|
await expect(nextButton).toBeVisible()
|
||||||
|
await nextButton.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish the onboarding
|
||||||
|
await expect(nextButton).toBeVisible()
|
||||||
|
await nextButton.click()
|
||||||
|
|
||||||
|
// Test that the onboarding pane is gone
|
||||||
|
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||||
|
await expect(page.url()).not.toContain('onboarding')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Onboarding redirects and code updating', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
// Give some initial code, so we can test that it's cleared
|
||||||
|
localStorage.setItem('persistCode', 'const sigmaAllow = 15000')
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// Test that the redirect happened
|
||||||
|
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||||
|
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test that you come back to this page when you refresh
|
||||||
|
await page.reload()
|
||||||
|
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||||
|
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test that the onboarding pane loaded
|
||||||
|
const title = page.locator('[data-testid="onboarding-content"]')
|
||||||
|
await expect(title).toBeAttached()
|
||||||
|
|
||||||
|
// Test that the code changes when you advance to the next step
|
||||||
|
await page.locator('[data-testid="onboarding-next"]').click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText('')
|
||||||
|
|
||||||
|
// Test that the code is not empty when you click on the next step
|
||||||
|
await page.locator('[data-testid="onboarding-next"]').click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Onboarding code gets reset to demo on Interactive Numbers step', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
test.skip(
|
||||||
|
process.platform === 'darwin',
|
||||||
|
"Skip on macOS, because Playwright isn't behaving the same as the actual browser"
|
||||||
|
)
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const badCode = `// This is bad code we shouldn't see`
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ settingsKey, settings, badCode }) => {
|
||||||
|
localStorage.setItem('persistCode', badCode)
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({
|
||||||
|
settings: TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING,
|
||||||
|
}),
|
||||||
|
badCode,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.waitForURL('**' + onboardingPaths.PARAMETRIC_MODELING, {
|
||||||
|
waitUntil: 'domcontentloaded',
|
||||||
|
})
|
||||||
|
|
||||||
|
const bracketNoNewLines = bracket.replace(/\n/g, '')
|
||||||
|
|
||||||
|
// Check the code got reset on load
|
||||||
|
await expect(page.locator('#code-pane')).toBeVisible()
|
||||||
|
await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mess with the code again
|
||||||
|
await u.codeLocator.selectText()
|
||||||
|
await u.codeLocator.fill(badCode)
|
||||||
|
await expect(u.codeLocator).toHaveText(badCode)
|
||||||
|
|
||||||
|
// Click to the next step
|
||||||
|
await page.locator('[data-testid="onboarding-next"]').click()
|
||||||
|
await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, {
|
||||||
|
waitUntil: 'domcontentloaded',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check that the code has been reset
|
||||||
|
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Avatar text updates depending on image load success', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({
|
||||||
|
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
|
// Test that the text in this step is correct
|
||||||
|
const avatarLocator = await page
|
||||||
|
.getByTestId('user-sidebar-toggle')
|
||||||
|
.locator('img')
|
||||||
|
const onboardingOverlayLocator = await page
|
||||||
|
.getByTestId('onboarding-content')
|
||||||
|
.locator('div')
|
||||||
|
.nth(1)
|
||||||
|
|
||||||
|
// Expect the avatar to be visible and for the text to reference it
|
||||||
|
await expect(avatarLocator).toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toContainText('your avatar')
|
||||||
|
|
||||||
|
// This is to force the avatar to 404.
|
||||||
|
// For our test image (only triggers locally. on CI, it's Kurt's /
|
||||||
|
// gravatar image )
|
||||||
|
await page.route('/cat.jpg', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 404,
|
||||||
|
contentType: 'text/plain',
|
||||||
|
body: 'Not Found!',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 404 the CI avatar image
|
||||||
|
await page.route('https://lh3.googleusercontent.com/**', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 404,
|
||||||
|
contentType: 'text/plain',
|
||||||
|
body: 'Not Found!',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
|
// Now expect the text to be different
|
||||||
|
await expect(avatarLocator).not.toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Avatar text doesn't mention avatar when no avatar", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({
|
||||||
|
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
|
// Test that the text in this step is correct
|
||||||
|
const sidebar = page.getByTestId('user-sidebar-toggle')
|
||||||
|
const avatar = sidebar.locator('img')
|
||||||
|
const onboardingOverlayLocator = page
|
||||||
|
.getByTestId('onboarding-content')
|
||||||
|
.locator('div')
|
||||||
|
.nth(1)
|
||||||
|
|
||||||
|
// Expect the avatar to be visible and for the text to reference it
|
||||||
|
await expect(avatar).not.toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
||||||
|
|
||||||
|
// Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939
|
||||||
|
// which doesn't deserver its own full test spun up
|
||||||
|
const userMenuFeatures = [
|
||||||
|
'manage your account',
|
||||||
|
'report a bug',
|
||||||
|
'request a feature',
|
||||||
|
'sign out',
|
||||||
|
]
|
||||||
|
for (const feature of userMenuFeatures) {
|
||||||
|
await expect(onboardingOverlayLocator).toContainText(feature)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
226
e2e/playwright/regression-tests.spec.ts
Normal file
226
e2e/playwright/regression-tests.spec.ts
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Regression tests', () => {
|
||||||
|
// bugs we found that don't fit neatly into other categories
|
||||||
|
test('bad model has inline error #3251', async ({ page }) => {
|
||||||
|
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
||||||
|
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
||||||
|
// Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch2 = startSketchOn("XY")
|
||||||
|
const sketch001 = startSketchAt([-0, -0])
|
||||||
|
|> line([0, 0], %)
|
||||||
|
|> line([-4.84, -5.29], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// error in guter
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
// this is a cryptic error message, fact that all the lines are co-linear from the `line([0,0])` is the issue why
|
||||||
|
// the close doesn't work
|
||||||
|
// when https://github.com/KittyCAD/modeling-app/issues/3268 is closed
|
||||||
|
// this test will need updating
|
||||||
|
const crypticErrorText = `ApiError`
|
||||||
|
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
||||||
|
})
|
||||||
|
test('executes on load', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([-6.95, 4.98], %)
|
||||||
|
|> line([25.1, 0.41], %)
|
||||||
|
|> line([0.73, -14.93], %)
|
||||||
|
|> line([-23.44, 0.52], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// expand variables section
|
||||||
|
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||||
|
await variablesTabButton.click()
|
||||||
|
|
||||||
|
// can find sketch001 in the variables summary (pretty-json-container, makes sure we're not looking in the code editor)
|
||||||
|
// sketch001 only shows up in the variables summary if it's been executed
|
||||||
|
await page.waitForFunction(() => {
|
||||||
|
const variablesElement = document.querySelector(
|
||||||
|
'.pretty-json-container'
|
||||||
|
) as HTMLDivElement
|
||||||
|
return variablesElement.innerHTML.includes('sketch001')
|
||||||
|
})
|
||||||
|
await expect(
|
||||||
|
page.locator('.pretty-json-container >> text=sketch001')
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('re-executes', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem('persistCode', `const myVar = 5`)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||||
|
await variablesTabButton.click()
|
||||||
|
// expect to see "myVar:5"
|
||||||
|
await expect(
|
||||||
|
page.locator('.pretty-json-container >> text=myVar:5')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// change 5 to 67
|
||||||
|
await page.getByText('const myVar').click()
|
||||||
|
await page.keyboard.press('End')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.keyboard.type('67')
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.locator('.pretty-json-container >> text=myVar:67')
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
test('ProgramMemory can be serialised', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([0, 1], %)
|
||||||
|
|> line([1, 0], %)
|
||||||
|
|> line([0, -1], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(1, %)
|
||||||
|
|> patternLinear3d({
|
||||||
|
axis: [1, 0, 1],
|
||||||
|
repetitions: 3,
|
||||||
|
distance: 6
|
||||||
|
}, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
const messages: string[] = []
|
||||||
|
|
||||||
|
// Listen for all console events and push the message text to an array
|
||||||
|
page.on('console', (message) => messages.push(message.text()))
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
|
const forbiddenMessages = ['cannot serialize tagged newtype variant']
|
||||||
|
forbiddenMessages.forEach((forbiddenMessage) => {
|
||||||
|
messages.forEach((message) => {
|
||||||
|
expect(message).not.toContain(forbiddenMessage)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test('ensure the Zoo logo is not a link in browser app', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const zooLogo = page.locator('[data-testid="app-logo"]')
|
||||||
|
// Make sure it's not a link
|
||||||
|
await expect(zooLogo).not.toHaveAttribute('href')
|
||||||
|
})
|
||||||
|
test('Position _ Is Out Of Range... regression test', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const exampleSketch = startSketchOn("XZ")
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> angledLine({ angle: 50, length: 45 }, %)
|
||||||
|
|> yLineTo(0, %)
|
||||||
|
|> close(%)
|
||||||
|
|>
|
||||||
|
|
||||||
|
const example = extrude(5, exampleSketch)
|
||||||
|
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
// error in guter
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||||
|
timeout: 1_000,
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// expect it still to be there (sometimes it just clears for a bit?)
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||||
|
timeout: 1_000,
|
||||||
|
})
|
||||||
|
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||||
|
|
||||||
|
// error text on hover
|
||||||
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||||
|
|
||||||
|
// Okay execution finished, let's start editing text below the error.
|
||||||
|
await u.codeLocator.click()
|
||||||
|
// Go to the end of the editor
|
||||||
|
// This bug happens when there is a diagnostic in the editor and you try to
|
||||||
|
// edit text below it.
|
||||||
|
// Or delete a huge chunk of text and then try to edit below it.
|
||||||
|
await page.keyboard.press('End')
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('End')
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type('thing: "blah"', { delay: 100 })
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('ArrowLeft')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toContainText(`const exampleSketch = startSketchOn("XZ")
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> angledLine({ angle: 50, length: 45 }, %)
|
||||||
|
|> yLineTo(0, %)
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
thing: "blah"`)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
959
e2e/playwright/sketch-tests.spec.ts
Normal file
959
e2e/playwright/sketch-tests.spec.ts
Normal file
@ -0,0 +1,959 @@
|
|||||||
|
import { test, expect, Page } from '@playwright/test'
|
||||||
|
|
||||||
|
import {
|
||||||
|
getMovementUtils,
|
||||||
|
getUtils,
|
||||||
|
PERSIST_MODELING_CONTEXT,
|
||||||
|
setup,
|
||||||
|
tearDown,
|
||||||
|
} from './test-utils'
|
||||||
|
import { uuidv4, roundOff } from 'lib/utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Sketch tests', () => {
|
||||||
|
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
}) => {
|
||||||
|
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',
|
||||||
|
`
|
||||||
|
const width = 20
|
||||||
|
const height = 10
|
||||||
|
const thickness = 5
|
||||||
|
const screwRadius = 3
|
||||||
|
const wireRadius = 2
|
||||||
|
const wireOffset = 0.5
|
||||||
|
|
||||||
|
const screwHole = startSketchOn('XY')
|
||||||
|
${startProfileAt1}
|
||||||
|
|> arc({
|
||||||
|
radius: screwRadius,
|
||||||
|
angle_start: 0,
|
||||||
|
angle_end: 360
|
||||||
|
}, %)
|
||||||
|
|
||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
${startProfileAt2}
|
||||||
|
|> xLine(width * .5, %)
|
||||||
|
|> yLine(height, %)
|
||||||
|
|> xLine(-width * .5, %)
|
||||||
|
|> close(%)
|
||||||
|
|> hole(screwHole, %)
|
||||||
|
|> extrude(thickness, %)
|
||||||
|
|
||||||
|
const part002 = startSketchOn('-XZ')
|
||||||
|
${startProfileAt3}
|
||||||
|
|> xLine(width / 4, %)
|
||||||
|
|> tangentialArcTo([width / 2, 0], %)
|
||||||
|
|> xLine(-width / 4 + wireRadius, %)
|
||||||
|
|> yLine(wireOffset, %)
|
||||||
|
|> arc({
|
||||||
|
radius: wireRadius,
|
||||||
|
angle_start: 0,
|
||||||
|
angle_end: 180
|
||||||
|
}, %)
|
||||||
|
|> yLine(-wireOffset, %)
|
||||||
|
|> xLine(-width / 4, %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(-height, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selectionsSnippets
|
||||||
|
)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// 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: 'Extrude' })).toBeDisabled()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
await page.getByText(selectionsSnippets.startProfileAt2).click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
await page.getByText(selectionsSnippets.startProfileAt3).click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||||
|
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,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([12.73, -0.09], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await expect(async () => {
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).toBeEnabled({ timeout: 1000 })
|
||||||
|
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([24.95, -5.38], %)').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.openAndClearDebugPanel()
|
||||||
|
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([0.31, 16.47], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
test('Can exit selection of face', async ({ page }) => {
|
||||||
|
// Load the app with the code panes
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem('persistCode', ``)
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
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,
|
||||||
|
openPanes: string[]
|
||||||
|
) => {
|
||||||
|
// Load the app with the code panes
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([12.73, -0.09], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
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(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([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 startProfieAt 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(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([6.44, -12.07], %)
|
||||||
|
|> line([14.72, 1.97], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> line([1.97, 2.06], %)
|
||||||
|
|> close(%)`)
|
||||||
|
}
|
||||||
|
test('code pane open at start-handles', async ({ page }) => {
|
||||||
|
// Load the app with the code panes
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'store',
|
||||||
|
JSON.stringify({
|
||||||
|
state: {
|
||||||
|
openPanes: ['code'],
|
||||||
|
},
|
||||||
|
version: 0,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await doEditSegmentsByDraggingHandle(page, ['code'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('code pane closed at start-handles', async ({ page }) => {
|
||||||
|
// Load the app with the code panes
|
||||||
|
await page.addInitScript(async (persistModelingContext) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
persistModelingContext,
|
||||||
|
JSON.stringify({ openPanes: [] })
|
||||||
|
)
|
||||||
|
}, PERSIST_MODELING_CONTEXT)
|
||||||
|
await doEditSegmentsByDraggingHandle(page, [])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// failing for the same reason as "Can undo a sketch modification with ctrl+z"
|
||||||
|
// please fix together
|
||||||
|
test.fixme(
|
||||||
|
'Can edit a sketch that has been extruded in the same pipe',
|
||||||
|
async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([12.73, -0.09], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
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 = 40
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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.waitForTimeout(100)
|
||||||
|
await page.dragAndDrop('#stream', '#stream', {
|
||||||
|
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||||
|
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
|
||||||
|
})
|
||||||
|
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, y: tangentEnd.y - 5 },
|
||||||
|
targetPosition: {
|
||||||
|
x: tangentEnd.x + dragPX,
|
||||||
|
y: tangentEnd.y + dragPX,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
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(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([7.12, -16.82], %)
|
||||||
|
|> line([15.4, -2.74], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> line([2.65, -2.69], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5, %)`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test('Can edit a sketch that has been revolved in the same pipe', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|
|> line([12.73, -0.09], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: "X",}, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
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 startProfieAt 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(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([6.44, -12.07], %)
|
||||||
|
|> line([14.72, 1.97], %)
|
||||||
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|
|> line([1.97, 2.06], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: "X" }, %)`)
|
||||||
|
})
|
||||||
|
test('Can add multiple sketches', async ({ page }) => {
|
||||||
|
test.skip(process.platform === 'darwin', 'Can add multiple sketches')
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
|
await page.setViewportSize(viewportSize)
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||||
|
const { toSU, 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 = "const 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 += ` |> startProfileAt(${toSU([0, 0])}, %)`
|
||||||
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
|
await click00r(50, 0)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
codeStr += ` |> line(${toSU([50, 0])}, %)`
|
||||||
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
|
await click00r(0, 50)
|
||||||
|
codeStr += ` |> line(${toSU([0, 50])}, %)`
|
||||||
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
|
await click00r(-50, 0)
|
||||||
|
codeStr += ` |> line(${toSU([-50, 0])}, %)`
|
||||||
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
|
// exit the sketch, reset relative clicker
|
||||||
|
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 += "const sketch002 = startSketchOn('XY')"
|
||||||
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await click00r(30, 0)
|
||||||
|
codeStr += ` |> startProfileAt([1.53, 0], %)`
|
||||||
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
|
await click00r(30, 0)
|
||||||
|
codeStr += ` |> line([1.53, 0], %)`
|
||||||
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
|
await click00r(0, 30)
|
||||||
|
codeStr += ` |> line([0, -1.53], %)`
|
||||||
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
|
await click00r(-30, 0)
|
||||||
|
codeStr += ` |> line([-1.53, 0], %)`
|
||||||
|
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.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
const code = `const sketch001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|
||||||
|
|> line([${roundOff(scale * 139.19)}, 0], %)
|
||||||
|
|> line([0, -${roundOff(scale * 139.2)}], %)
|
||||||
|
|> lineTo([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(
|
||||||
|
`const 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(page.locator('.cm-content')).toHaveText(code)
|
||||||
|
// Assert the tool was unequipped
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Line', exact: true })
|
||||||
|
).not.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 }) => {
|
||||||
|
await doSnapAtDifferentScales(page, [0, 100, 100], 0.01)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('[0, 10000, 10000]', async ({ page }) => {
|
||||||
|
await doSnapAtDifferentScales(page, [0, 10000, 10000])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test('exiting a close extrude, has the extrude button enabled ready to go', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-0.45, 0.87], %)
|
||||||
|
|> line([1.32, 0.38], %)
|
||||||
|
|> line([1.02, -1.32], %, $seg01)
|
||||||
|
|> line([-1.01, -0.77], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// click "line([1.32, 0.38], %)"
|
||||||
|
await page.getByText(`line([1.32, 0.38], %)`).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// 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 face', exact: false })
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
test("Existing sketch with bad code delete user's code", async ({ page }) => {
|
||||||
|
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-0.45, 0.87], %)
|
||||||
|
|> line([1.32, 0.38], %)
|
||||||
|
|> line([1.02, -1.32], %, $seg01)
|
||||||
|
|> line([-1.01, -0.77], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(5, sketch001)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
|
await page.mouse.click(622, 355)
|
||||||
|
|
||||||
|
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(
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-0.45, 0.87], %)
|
||||||
|
|> line([1.32, 0.38], %)
|
||||||
|
|> line([1.02, -1.32], %, $seg01)
|
||||||
|
|> line([-1.01, -0.77], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(5, sketch001)
|
||||||
|
const sketch002 = startSketchOn(extrude001, 'END')
|
||||||
|
|>
|
||||||
|
`.replace(/\s/g, '')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
test('empty-scene default-planes act as expected', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
test.skip(
|
||||||
|
browserName === 'webkit',
|
||||||
|
'Skip on Safari until `window.tearDown` is working there'
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* 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 page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
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(`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([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: 5 })
|
||||||
|
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(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([11.8, 9.09], %)
|
||||||
|
|> line([3.39, -3.39], %)
|
||||||
|
`)
|
||||||
|
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([11.8, 9.09], %)
|
||||||
|
|> line([3.39, -3.39], %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.reload()
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
237
e2e/playwright/test-network-and-connection-issues.spec.ts
Normal file
237
e2e/playwright/test-network-and-connection-issues.spec.ts
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { commonPoints, getUtils, setup, tearDown } from './test-utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Test network and connection issues', () => {
|
||||||
|
test('simulate network down and network little widget', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
||||||
|
test.skip(
|
||||||
|
browserName === 'webkit',
|
||||||
|
'Skip on Safari until `window.tearDown` is working there'
|
||||||
|
)
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
|
|
||||||
|
// This is how we wait until the stream is online
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled({ timeout: 15000 })
|
||||||
|
|
||||||
|
const networkWidget = page.locator('[data-testid="network-toggle"]')
|
||||||
|
await expect(networkWidget).toBeVisible()
|
||||||
|
await networkWidget.hover()
|
||||||
|
|
||||||
|
const networkPopover = page.locator('[data-testid="network-popover"]')
|
||||||
|
await expect(networkPopover).not.toBeVisible()
|
||||||
|
|
||||||
|
// (First check) Expect the network to be up
|
||||||
|
await expect(networkToggle).toContainText('Connected')
|
||||||
|
|
||||||
|
// Click the network widget
|
||||||
|
await networkWidget.click()
|
||||||
|
|
||||||
|
// Check the modal opened.
|
||||||
|
await expect(networkPopover).toBeVisible()
|
||||||
|
|
||||||
|
// Click off the modal.
|
||||||
|
await page.mouse.click(100, 100)
|
||||||
|
await expect(networkPopover).not.toBeVisible()
|
||||||
|
|
||||||
|
// Turn off the network
|
||||||
|
await u.emulateNetworkConditions({
|
||||||
|
offline: true,
|
||||||
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
|
latency: 0,
|
||||||
|
downloadThroughput: -1,
|
||||||
|
uploadThroughput: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Expect the network to be down
|
||||||
|
await expect(networkToggle).toContainText('Offline')
|
||||||
|
|
||||||
|
// Click the network widget
|
||||||
|
await networkWidget.click()
|
||||||
|
|
||||||
|
// Check the modal opened.
|
||||||
|
await expect(networkPopover).toBeVisible()
|
||||||
|
|
||||||
|
// Click off the modal.
|
||||||
|
await page.mouse.click(0, 0)
|
||||||
|
await expect(networkPopover).not.toBeVisible()
|
||||||
|
|
||||||
|
// Turn back on the network
|
||||||
|
await u.emulateNetworkConditions({
|
||||||
|
offline: false,
|
||||||
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
|
latency: 0,
|
||||||
|
downloadThroughput: -1,
|
||||||
|
uploadThroughput: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled({ timeout: 15000 })
|
||||||
|
|
||||||
|
// (Second check) expect the network to be up
|
||||||
|
await expect(networkToggle).toContainText('Connected')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Engine disconnect & reconnect in sketch mode', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
||||||
|
test.skip(
|
||||||
|
browserName === 'webkit',
|
||||||
|
'Skip on Safari until `window.tearDown` is working there'
|
||||||
|
)
|
||||||
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled({ timeout: 15000 })
|
||||||
|
|
||||||
|
// click on "Start Sketch" button
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// select a plane
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const sketch001 = startSketchOn('XZ')`
|
||||||
|
)
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
|
const startXPx = 600
|
||||||
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
|
||||||
|
// Expect the network to be up
|
||||||
|
await expect(networkToggle).toContainText('Connected')
|
||||||
|
|
||||||
|
// simulate network down
|
||||||
|
await u.emulateNetworkConditions({
|
||||||
|
offline: true,
|
||||||
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
|
latency: 0,
|
||||||
|
downloadThroughput: -1,
|
||||||
|
uploadThroughput: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Expect the network to be down
|
||||||
|
await expect(networkToggle).toContainText('Offline')
|
||||||
|
|
||||||
|
// Ensure we are not in sketch mode
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
).not.toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// simulate network up
|
||||||
|
await u.emulateNetworkConditions({
|
||||||
|
offline: false,
|
||||||
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
|
latency: 0,
|
||||||
|
downloadThroughput: -1,
|
||||||
|
uploadThroughput: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wait for the app to be ready for use
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled({ timeout: 15000 })
|
||||||
|
|
||||||
|
// Expect the network to be up
|
||||||
|
await expect(networkToggle).toContainText('Connected')
|
||||||
|
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
|
||||||
|
|
||||||
|
// Click off the code pane.
|
||||||
|
await page.mouse.click(100, 100)
|
||||||
|
|
||||||
|
// select a line
|
||||||
|
await page.getByText(`startProfileAt(${commonPoints.startAt}, %)`).click()
|
||||||
|
|
||||||
|
// enter sketch again
|
||||||
|
await u.doAndWaitForCmd(
|
||||||
|
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
||||||
|
'default_camera_get_settings'
|
||||||
|
)
|
||||||
|
await page.waitForTimeout(150)
|
||||||
|
|
||||||
|
// Click the line tool
|
||||||
|
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(150)
|
||||||
|
|
||||||
|
// Ensure we can continue sketching
|
||||||
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|
|> line([-8.84, 8.75], %)`)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|
|> line([-8.84, 8.75], %)
|
||||||
|
|> line([-5.6, 0], %)`)
|
||||||
|
|
||||||
|
// Unequip line tool
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
// Make sure we didn't pop out of sketch mode.
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Line', exact: true })
|
||||||
|
).not.toHaveAttribute('aria-pressed', 'true')
|
||||||
|
|
||||||
|
// Exit sketch
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
).not.toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
@ -1,4 +1,10 @@
|
|||||||
import { expect, Page, Download } from '@playwright/test'
|
import {
|
||||||
|
expect,
|
||||||
|
Page,
|
||||||
|
Download,
|
||||||
|
TestInfo,
|
||||||
|
BrowserContext,
|
||||||
|
} from '@playwright/test'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
@ -7,6 +13,10 @@ import { PNG } from 'pngjs'
|
|||||||
import { Protocol } from 'playwright-core/types/protocol'
|
import { Protocol } from 'playwright-core/types/protocol'
|
||||||
import type { Models } from '@kittycad/lib'
|
import type { Models } from '@kittycad/lib'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
|
import waitOn from 'wait-on'
|
||||||
|
import { secrets } from './secrets'
|
||||||
|
import { TEST_SETTINGS_KEY, TEST_SETTINGS } from './storageStates'
|
||||||
|
import * as TOML from '@iarna/toml'
|
||||||
|
|
||||||
type TestColor = [number, number, number]
|
type TestColor = [number, number, number]
|
||||||
export const TEST_COLORS = {
|
export const TEST_COLORS = {
|
||||||
@ -15,6 +25,16 @@ export const TEST_COLORS = {
|
|||||||
BLUE: [0, 0, 255] as TestColor,
|
BLUE: [0, 0, 255] as TestColor,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
export const PERSIST_MODELING_CONTEXT = 'persistModelingContext'
|
||||||
|
|
||||||
|
export const deg = (Math.PI * 2) / 360
|
||||||
|
|
||||||
|
export const commonPoints = {
|
||||||
|
startAt: '[7.19, -9.7]',
|
||||||
|
num1: 7.25,
|
||||||
|
num2: 14.44,
|
||||||
|
}
|
||||||
|
|
||||||
async function waitForPageLoadWithRetry(page: Page) {
|
async function waitForPageLoadWithRetry(page: Page) {
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
@ -563,3 +583,46 @@ export const doExport = async (
|
|||||||
* Gets the appropriate modifier key for the platform.
|
* Gets the appropriate modifier key for the platform.
|
||||||
*/
|
*/
|
||||||
export const metaModifier = os.platform() === 'darwin' ? 'Meta' : 'Control'
|
export const metaModifier = os.platform() === 'darwin' ? 'Meta' : 'Control'
|
||||||
|
|
||||||
|
export async function tearDown(page: Page, testInfo: TestInfo) {
|
||||||
|
if (testInfo.status === 'skipped') return
|
||||||
|
if (testInfo.status === 'failed') return
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// Kill the network so shutdown happens properly
|
||||||
|
await u.emulateNetworkConditions({
|
||||||
|
offline: true,
|
||||||
|
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||||
|
latency: 0,
|
||||||
|
downloadThroughput: -1,
|
||||||
|
uploadThroughput: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// It seems it's best to give the browser about 3s to close things
|
||||||
|
// It's not super reliable but we have no real other choice for now
|
||||||
|
await page.waitForTimeout(3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setup(context: BrowserContext, page: Page) {
|
||||||
|
// wait for Vite preview server to be up
|
||||||
|
await waitOn({
|
||||||
|
resources: ['tcp:3000'],
|
||||||
|
timeout: 5000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await context.addInitScript(
|
||||||
|
async ({ token, settingsKey, settings }) => {
|
||||||
|
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||||
|
localStorage.setItem('persistCode', ``)
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
localStorage.setItem('playwright', 'true')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
token: secrets.token,
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({ settings: TEST_SETTINGS }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// kill animations, speeds up tests and reduced flakiness
|
||||||
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||||
|
}
|
||||||
|
339
e2e/playwright/testing-camera-movement.spec.ts
Normal file
339
e2e/playwright/testing-camera-movement.spec.ts
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Testing Camera Movement', () => {
|
||||||
|
test('Can moving camera', async ({ page, context }) => {
|
||||||
|
test.skip(process.platform === 'darwin', 'Can moving camera')
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await u.closeKclCodePanel()
|
||||||
|
|
||||||
|
const camPos: [number, number, number] = [0, 85, 85]
|
||||||
|
const bakeInRetries = async (
|
||||||
|
mouseActions: any,
|
||||||
|
xyz: [number, number, number],
|
||||||
|
cnt = 0
|
||||||
|
) => {
|
||||||
|
// hack that we're implemented our own retry instead of using retries built into playwright.
|
||||||
|
// however each of these camera drags can be flaky, because of udp
|
||||||
|
// and so putting them together means only one needs to fail to make this test extra flaky.
|
||||||
|
// this way we can retry within the test
|
||||||
|
// We could break them out into separate tests, but the longest past of the test is waiting
|
||||||
|
// for the stream to start, so it can be good to bundle related things together.
|
||||||
|
|
||||||
|
const camCommand: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
center: { x: 0, y: 0, z: 0 },
|
||||||
|
vantage: { x: camPos[0], y: camPos[1], z: camPos[2] },
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const updateCamCommand: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await u.sendCustomCmd(camCommand)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.sendCustomCmd(updateCamCommand)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// rotate
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// const yo = page.getByTestId('cam-x-position').inputValue()
|
||||||
|
|
||||||
|
await u.doAndWaitForImageDiff(async () => {
|
||||||
|
await mouseActions()
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByTestId('cam-x-position').isVisible()
|
||||||
|
|
||||||
|
const vals = await Promise.all([
|
||||||
|
page.getByTestId('cam-x-position').inputValue(),
|
||||||
|
page.getByTestId('cam-y-position').inputValue(),
|
||||||
|
page.getByTestId('cam-z-position').inputValue(),
|
||||||
|
])
|
||||||
|
const xError = Math.abs(Number(vals[0]) + xyz[0])
|
||||||
|
const yError = Math.abs(Number(vals[1]) + xyz[1])
|
||||||
|
const zError = Math.abs(Number(vals[2]) + xyz[2])
|
||||||
|
|
||||||
|
let shouldRetry = false
|
||||||
|
|
||||||
|
if (xError > 5 || yError > 5 || zError > 5) {
|
||||||
|
if (cnt > 2) {
|
||||||
|
console.log('xVal', vals[0], 'xError', xError)
|
||||||
|
console.log('yVal', vals[1], 'yError', yError)
|
||||||
|
console.log('zVal', vals[2], 'zError', zError)
|
||||||
|
|
||||||
|
throw new Error('Camera position not as expected')
|
||||||
|
}
|
||||||
|
shouldRetry = true
|
||||||
|
}
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
if (shouldRetry) await bakeInRetries(mouseActions, xyz, cnt + 1)
|
||||||
|
}
|
||||||
|
await bakeInRetries(async () => {
|
||||||
|
await page.mouse.move(700, 200)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(600, 303)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
}, [4, -10.5, -120])
|
||||||
|
|
||||||
|
await bakeInRetries(async () => {
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.move(600, 200)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(700, 200, { steps: 2 })
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
}, [-19, -85, -85])
|
||||||
|
|
||||||
|
const camCommand: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
center: { x: 0, y: 0, z: 0 },
|
||||||
|
vantage: { x: camPos[0], y: camPos[1], z: camPos[2] },
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const updateCamCommand: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
await u.sendCustomCmd(camCommand)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.sendCustomCmd(updateCamCommand)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
// zoom
|
||||||
|
await u.doAndWaitForImageDiff(async () => {
|
||||||
|
await page.keyboard.down('Control')
|
||||||
|
await page.mouse.move(700, 400)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(700, 300)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
// zoom with scroll
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
// TODO, it appears we don't get the cam setting back from the engine when the interaction is zoom into `backInRetries` once the information is sent back on zoom
|
||||||
|
// await expect(Math.abs(Number(await page.getByTestId('cam-x-position').inputValue()) + 12)).toBeLessThan(1.5)
|
||||||
|
// await expect(Math.abs(Number(await page.getByTestId('cam-y-position').inputValue()) - 85)).toBeLessThan(1.5)
|
||||||
|
// await expect(Math.abs(Number(await page.getByTestId('cam-z-position').inputValue()) - 85)).toBeLessThan(1.5)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
|
||||||
|
await bakeInRetries(async () => {
|
||||||
|
await page.mouse.move(700, 400)
|
||||||
|
await page.mouse.wheel(0, -100)
|
||||||
|
}, [0, -85, -85])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Zoom should be consistent when exiting or entering sketches', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
||||||
|
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
||||||
|
// than again for sketching
|
||||||
|
|
||||||
|
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
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 page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// select a plane
|
||||||
|
await page.mouse.click(700, 325)
|
||||||
|
|
||||||
|
let code = `const sketch001 = startSketchOn('XY')`
|
||||||
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
|
// move the camera slightly
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.move(700, 300)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(800, 200)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
|
let y = 350,
|
||||||
|
x = 948
|
||||||
|
|
||||||
|
await u.canvasLocator.click({ position: { x: 783, y } })
|
||||||
|
code += `\n |> startProfileAt([8.12, -12.98], %)`
|
||||||
|
// await expect(u.codeLocator).toHaveText(code)
|
||||||
|
await u.canvasLocator.click({ position: { x, y } })
|
||||||
|
code += `\n |> line([11.18, 0], %)`
|
||||||
|
// await expect(u.codeLocator).toHaveText(code)
|
||||||
|
await u.canvasLocator.click({ position: { x, y: 275 } })
|
||||||
|
code += `\n |> line([0, 6.99], %)`
|
||||||
|
// await expect(u.codeLocator).toHaveText(code)
|
||||||
|
|
||||||
|
// click the line button
|
||||||
|
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||||
|
|
||||||
|
const hoverOverNothing = async () => {
|
||||||
|
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
|
||||||
|
await page.mouse.move(700, 325)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// hover over horizontal line
|
||||||
|
await u.canvasLocator.hover({ position: { x: 800, y } })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// hover over vertical line
|
||||||
|
await u.canvasLocator.hover({ position: { x, y: 325 } })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
// click exit sketch
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// hover over horizontal line
|
||||||
|
await page.mouse.move(858, y, { steps: 5 })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
// hover over vertical line
|
||||||
|
await page.mouse.move(x, 325)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
// hover over vertical line
|
||||||
|
await page.mouse.move(857, y)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
// now click it
|
||||||
|
await page.mouse.click(857, y)
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
x = 975
|
||||||
|
y = 468
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.move(x, 419, { steps: 5 })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
await page.mouse.move(855, y)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await page.mouse.move(x, 419)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
await page.mouse.move(855, y)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
938
e2e/playwright/testing-constraints.spec.ts
Normal file
938
e2e/playwright/testing-constraints.spec.ts
Normal file
@ -0,0 +1,938 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { getUtils, setup, tearDown, TEST_COLORS } from './test-utils'
|
||||||
|
import { XOR } from 'lib/utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Testing constraints', () => {
|
||||||
|
test('Can constrain line length', async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> xLine(-20, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// Click the line of code for line.
|
||||||
|
await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// enter sketch again
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(500) // wait for animation
|
||||||
|
|
||||||
|
const startXPx = 500
|
||||||
|
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.click(834, 244)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Length', exact: true }).click()
|
||||||
|
await page.getByText('Add constraining value').click()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
|
`const length001 = 20const sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Make sure we didn't pop out of sketch mode.
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
await page.waitForTimeout(500) // wait for animation
|
||||||
|
|
||||||
|
// Exit sketch
|
||||||
|
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
).not.toBeVisible()
|
||||||
|
})
|
||||||
|
test(`Test remove constraints`, async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 79
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %, $seg01)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> angledLine([segAng(seg01), yo], %)
|
||||||
|
|> line([41.19, 28.97 + 5], %)
|
||||||
|
const part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const line3 = await u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`)
|
||||||
|
|
||||||
|
await page.mouse.click(line3.x, line3.y)
|
||||||
|
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page.getByRole('button', { name: 'remove constraints' }).click()
|
||||||
|
|
||||||
|
await page.getByText('line([39.13, 68.63], %)').click()
|
||||||
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await expect(activeLinesContent).toHaveLength(1)
|
||||||
|
await expect(activeLinesContent[0]).toHaveText('|> line([39.13, 68.63], %)')
|
||||||
|
|
||||||
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
|
})
|
||||||
|
test.describe('Test perpendicular distance constraint', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
testName: 'Add variable',
|
||||||
|
offset: '-offset001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'No variable',
|
||||||
|
offset: '-128.05',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { testName, offset } of cases) {
|
||||||
|
test(`${testName}`, async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 5
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %, $seg01)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> angledLine([segAng(seg01), 78.33], %)
|
||||||
|
|> line([41.19, 28.97], %)
|
||||||
|
const part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const [line1, line3] = await Promise.all([
|
||||||
|
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||||
|
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||||
|
])
|
||||||
|
|
||||||
|
await page.mouse.click(line1.x, line1.y)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.click(line3.x, line3.y)
|
||||||
|
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Perpendicular Distance' })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
const createNewVariableCheckbox = page.getByTestId(
|
||||||
|
'create-new-variable-checkbox'
|
||||||
|
)
|
||||||
|
const isChecked = await createNewVariableCheckbox.isChecked()
|
||||||
|
const addVariable = testName === 'Add variable'
|
||||||
|
XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct
|
||||||
|
(await createNewVariableCheckbox.click())
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Add constraining value' })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// Wait for the codemod to take effect
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(`angle: -57,`)
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
`offset: ${offset},`
|
||||||
|
)
|
||||||
|
|
||||||
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await expect(activeLinesContent[0]).toHaveText(
|
||||||
|
`|> line([74.36, 130.4], %, $seg01)`
|
||||||
|
)
|
||||||
|
await expect(activeLinesContent[1]).toHaveText(`}, %)`)
|
||||||
|
|
||||||
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test.describe('Test distance between constraint', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
testName: 'Add variable',
|
||||||
|
constraint: 'horizontal distance',
|
||||||
|
value: 'segEndX(seg01) + xDis001, 61.34',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'No variable',
|
||||||
|
constraint: 'horizontal distance',
|
||||||
|
value: 'segEndX(seg01) + 88.08, 61.34',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'Add variable',
|
||||||
|
constraint: 'vertical distance',
|
||||||
|
value: '154.9, segEndY(seg01) - yDis001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'No variable',
|
||||||
|
constraint: 'vertical distance',
|
||||||
|
value: '154.9, segEndY(seg01) - 42.32',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { testName, value, constraint } of cases) {
|
||||||
|
test(`${constraint} - ${testName}`, async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 5
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> line([9.16, 77.79], %)
|
||||||
|
|> line([41.19, 28.97], %)
|
||||||
|
const part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const [line1, line3] = await Promise.all([
|
||||||
|
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||||
|
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||||
|
])
|
||||||
|
|
||||||
|
await page.mouse.click(line1.x, line1.y)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.click(line3.x, line3.y)
|
||||||
|
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page.getByRole('button', { name: constraint }).click()
|
||||||
|
|
||||||
|
const createNewVariableCheckbox = page.getByTestId(
|
||||||
|
'create-new-variable-checkbox'
|
||||||
|
)
|
||||||
|
const isChecked = await createNewVariableCheckbox.isChecked()
|
||||||
|
const addVariable = testName === 'Add variable'
|
||||||
|
XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct
|
||||||
|
(await createNewVariableCheckbox.click())
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Add constraining value' })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// checking activeLines assures the cursors are where they should be
|
||||||
|
const codeAfter = [
|
||||||
|
`|> line([74.36, 130.4], %, $seg01)`,
|
||||||
|
`|> lineTo([${value}], %)`,
|
||||||
|
]
|
||||||
|
|
||||||
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await Promise.all(
|
||||||
|
activeLinesContent.map(async (line, i) => {
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
codeAfter[i]
|
||||||
|
)
|
||||||
|
// if the code is an active line then the cursor should be on that line
|
||||||
|
await expect(line).toHaveText(codeAfter[i])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test.describe('Test ABS distance constraint', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
testName: 'Add variable',
|
||||||
|
addVariable: true,
|
||||||
|
constraint: 'Absolute X',
|
||||||
|
value: 'xDis001, 61.34',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'No variable',
|
||||||
|
addVariable: false,
|
||||||
|
constraint: 'Absolute X',
|
||||||
|
value: '154.9, 61.34',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'Add variable',
|
||||||
|
addVariable: true,
|
||||||
|
constraint: 'Absolute Y',
|
||||||
|
value: '154.9, yDis001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'No variable',
|
||||||
|
addVariable: false,
|
||||||
|
constraint: 'Absolute Y',
|
||||||
|
value: '154.9, 61.34',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
|
test(`${constraint} - ${testName}`, async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 5
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> line([9.16, 77.79], %)
|
||||||
|
|> line([41.19, 28.97], %)
|
||||||
|
const part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const [line3] = await Promise.all([
|
||||||
|
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (constraint === 'Absolute X') {
|
||||||
|
await page.mouse.click(600, 130)
|
||||||
|
} else {
|
||||||
|
await page.mouse.click(900, 250)
|
||||||
|
}
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.click(line3.x, line3.y)
|
||||||
|
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: constraint, exact: true })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
const createNewVariableCheckbox = page.getByTestId(
|
||||||
|
'create-new-variable-checkbox'
|
||||||
|
)
|
||||||
|
const isChecked = await createNewVariableCheckbox.isChecked()
|
||||||
|
XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct
|
||||||
|
(await createNewVariableCheckbox.click())
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Add constraining value' })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// checking activeLines assures the cursors are where they should be
|
||||||
|
const codeAfter = [`|> lineTo([${value}], %)`]
|
||||||
|
|
||||||
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await Promise.all(
|
||||||
|
activeLinesContent.map(async (line, i) => {
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
codeAfter[i]
|
||||||
|
)
|
||||||
|
// if the code is an active line then the cursor should be on that line
|
||||||
|
await expect(line).toHaveText(codeAfter[i])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test.describe('Test Angle constraint double segment selection', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
testName: 'Add variable',
|
||||||
|
addVariable: true,
|
||||||
|
axisSelect: false,
|
||||||
|
value: 'segAng(seg01) + angle001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'No variable',
|
||||||
|
addVariable: false,
|
||||||
|
axisSelect: false,
|
||||||
|
value: 'segAng(seg01) + 22.69',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'Add variable, selecting axis',
|
||||||
|
addVariable: true,
|
||||||
|
axisSelect: true,
|
||||||
|
value: 'QUARTER_TURN - angle001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'No variable, selecting axis',
|
||||||
|
addVariable: false,
|
||||||
|
axisSelect: true,
|
||||||
|
value: 'QUARTER_TURN - 7',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { testName, addVariable, value, axisSelect } of cases) {
|
||||||
|
test(`${testName}`, async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 5
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> line([9.16, 77.79], %)
|
||||||
|
|> line([41.19, 28.97], %)
|
||||||
|
const part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const [line1, line3] = await Promise.all([
|
||||||
|
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||||
|
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (axisSelect) {
|
||||||
|
await page.mouse.click(600, 130)
|
||||||
|
} else {
|
||||||
|
await page.mouse.click(line1.x, line1.y)
|
||||||
|
}
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.click(line3.x, line3.y)
|
||||||
|
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page.getByTestId('dropdown-constraint-angle').click()
|
||||||
|
|
||||||
|
const createNewVariableCheckbox = page.getByTestId(
|
||||||
|
'create-new-variable-checkbox'
|
||||||
|
)
|
||||||
|
const isChecked = await createNewVariableCheckbox.isChecked()
|
||||||
|
XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct
|
||||||
|
(await createNewVariableCheckbox.click())
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Add constraining value' })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// checking activeLines assures the cursors are where they should be
|
||||||
|
const codeAfter = [
|
||||||
|
'|> line([74.36, 130.4], %, $seg01)',
|
||||||
|
`|> angledLine([${value}, 78.33], %)`,
|
||||||
|
]
|
||||||
|
if (axisSelect) codeAfter.shift()
|
||||||
|
|
||||||
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await Promise.all(
|
||||||
|
activeLinesContent.map(async (line, i) => {
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
codeAfter[i]
|
||||||
|
)
|
||||||
|
// if the code is an active line then the cursor should be on that line
|
||||||
|
await expect(line).toHaveText(codeAfter[i])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test.describe('Test Angle/Length constraint single selection', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
testName: 'Angle - Add variable',
|
||||||
|
addVariable: true,
|
||||||
|
constraint: 'angle',
|
||||||
|
value: 'angle001, 78.33',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'Angle - No variable',
|
||||||
|
addVariable: false,
|
||||||
|
constraint: 'angle',
|
||||||
|
value: '83, 78.33',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'Length - Add variable',
|
||||||
|
addVariable: true,
|
||||||
|
constraint: 'length',
|
||||||
|
value: '83, length001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: 'Length - No variable',
|
||||||
|
addVariable: false,
|
||||||
|
constraint: 'length',
|
||||||
|
value: '83, 78.33',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
|
test(`${testName}`, async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 5
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> line([9.16, 77.79], %)
|
||||||
|
|> line([41.19, 28.97], %)
|
||||||
|
const part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const line3 = await u.getSegmentBodyCoords(
|
||||||
|
`[data-overlay-index="${2}"]`
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.mouse.click(line3.x, line3.y)
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page.getByTestId('dropdown-constraint-' + constraint).click()
|
||||||
|
|
||||||
|
if (!addVariable) {
|
||||||
|
await page.getByTestId('create-new-variable-checkbox').click()
|
||||||
|
}
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Add constraining value' })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
const changedCode = `|> angledLine([${value}], %)`
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(changedCode)
|
||||||
|
// checking active assures the cursor is where it should be
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
|
||||||
|
|
||||||
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test.describe('Many segments - no modal constraints', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
constraintName: 'Vertical',
|
||||||
|
codeAfter: [
|
||||||
|
`|> yLine(130.4, %)`,
|
||||||
|
`|> yLine(77.79, %)`,
|
||||||
|
`|> yLine(28.97, %)`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codeAfter: [
|
||||||
|
`|> xLine(74.36, %)`,
|
||||||
|
`|> xLine(9.16, %)`,
|
||||||
|
`|> xLine(41.19, %)`,
|
||||||
|
],
|
||||||
|
constraintName: 'Horizontal',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { codeAfter, constraintName } of cases) {
|
||||||
|
test(`${constraintName}`, async ({ page }) => {
|
||||||
|
await page.addInitScript(async (customCode) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 5
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> line([9.16, 77.79], %)
|
||||||
|
|> line([41.19, 28.97], %)
|
||||||
|
const part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const line1 = await u.getSegmentBodyCoords(
|
||||||
|
`[data-overlay-index="${0}"]`
|
||||||
|
)
|
||||||
|
const line3 = await u.getSegmentBodyCoords(
|
||||||
|
`[data-overlay-index="${2}"]`
|
||||||
|
)
|
||||||
|
const line4 = await u.getSegmentBodyCoords(
|
||||||
|
`[data-overlay-index="${3}"]`
|
||||||
|
)
|
||||||
|
|
||||||
|
// select two segments by holding down shift
|
||||||
|
await page.mouse.click(line1.x, line1.y)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.click(line3.x, line3.y)
|
||||||
|
await page.mouse.click(line4.x, line4.y)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
|
// check actives lines
|
||||||
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await expect(activeLinesContent).toHaveLength(codeAfter.length)
|
||||||
|
|
||||||
|
const constraintMenuButton = page.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
const constraintButton = page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: constraintName,
|
||||||
|
})
|
||||||
|
.first()
|
||||||
|
|
||||||
|
// apply the constraint
|
||||||
|
await constraintMenuButton.click()
|
||||||
|
await constraintButton.click({ delay: 200 })
|
||||||
|
|
||||||
|
// check there are still 3 cursors (they should stay on the same lines as before constraint was applied)
|
||||||
|
await expect(page.locator('.cm-cursor')).toHaveCount(codeAfter.length)
|
||||||
|
|
||||||
|
// check both cursors are where they should be after constraint is applied and the code is correct
|
||||||
|
await Promise.all(
|
||||||
|
activeLinesContent.map(async (line, i) => {
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
codeAfter[i]
|
||||||
|
)
|
||||||
|
// if the code is an active line then the cursor should be on that line
|
||||||
|
await expect(line).toHaveText(codeAfter[i])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test.describe('Two segment - no modal constraints', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
codeAfter: `|> angledLine([83, segLen(seg01)], %)`,
|
||||||
|
constraintName: 'Equal Length',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codeAfter: `|> angledLine([segAng(seg01), 78.33], %)`,
|
||||||
|
constraintName: 'Parallel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codeAfter: `|> lineTo([segEndX(seg01), 61.34], %)`,
|
||||||
|
constraintName: 'Vertically Align',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codeAfter: `|> lineTo([154.9, segEndY(seg01)], %)`,
|
||||||
|
constraintName: 'Horizontally Align',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { codeAfter, constraintName } of cases) {
|
||||||
|
test(`${constraintName}`, async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 5
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> line([9.16, 77.79], %)
|
||||||
|
const part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const line1 = await u.getBoundingBox(`[data-overlay-index="${0}"]`)
|
||||||
|
const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`)
|
||||||
|
|
||||||
|
// select two segments by holding down shift
|
||||||
|
await page.mouse.click(line1.x - 20, line1.y + 20)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.click(line3.x - 3, line3.y + 20)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
const constraintMenuButton = page.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
const constraintButton = page.getByRole('button', {
|
||||||
|
name: constraintName,
|
||||||
|
})
|
||||||
|
|
||||||
|
// apply the constraint
|
||||||
|
await constraintMenuButton.click()
|
||||||
|
await constraintButton.click()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(codeAfter)
|
||||||
|
// expect the string 'seg01' to appear twice in '.cm-content' the tag segment and referencing the tag
|
||||||
|
const content = await page.locator('.cm-content').innerText()
|
||||||
|
await expect(content.match(/seg01/g)).toHaveLength(2)
|
||||||
|
// check there are still 2 cursors (they should stay on the same lines as before constraint was applied)
|
||||||
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
|
// check actives lines
|
||||||
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await expect(activeLinesContent).toHaveLength(2)
|
||||||
|
|
||||||
|
// check both cursors are where they should be after constraint is applied
|
||||||
|
await expect(activeLinesContent[0]).toHaveText(
|
||||||
|
'|> line([74.36, 130.4], %, $seg01)'
|
||||||
|
)
|
||||||
|
await expect(activeLinesContent[1]).toHaveText(codeAfter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test.describe('Axis & segment - no modal constraints', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
codeAfter: `|> lineTo([154.9, ZERO], %)`,
|
||||||
|
axisClick: { x: 950, y: 250 },
|
||||||
|
constraintName: 'Snap To X',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codeAfter: `|> lineTo([ZERO, 61.34], %)`,
|
||||||
|
axisClick: { x: 600, y: 150 },
|
||||||
|
constraintName: 'Snap To Y',
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { codeAfter, constraintName, axisClick } of cases) {
|
||||||
|
test(`${constraintName}`, async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const yo = 5
|
||||||
|
const part001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|
|> line([74.36, 130.4], %)
|
||||||
|
|> line([78.92, -120.11], %)
|
||||||
|
|> line([9.16, 77.79], %)
|
||||||
|
const part002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|
|> yLine(-264.06, %)
|
||||||
|
|> xLine(segLen(seg_what), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`)
|
||||||
|
|
||||||
|
// select segment and axis by holding down shift
|
||||||
|
await page.mouse.click(line3.x - 3, line3.y + 20)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(axisClick.x, axisClick.y)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
const constraintMenuButton = page.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
const constraintButton = page.getByRole('button', {
|
||||||
|
name: constraintName,
|
||||||
|
})
|
||||||
|
|
||||||
|
// apply the constraint
|
||||||
|
await constraintMenuButton.click()
|
||||||
|
await expect(constraintButton).toBeVisible()
|
||||||
|
await constraintButton.click()
|
||||||
|
|
||||||
|
// check the cursor is where is should be after constraint is applied
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(codeAfter)
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(codeAfter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Horizontally constrained line remains selected after applying constraint', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
test.setTimeout(70_000)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-1.05, -1.07], %)
|
||||||
|
|> line([3.79, 2.68], %, $seg01)
|
||||||
|
|> line([3.13, -2.4], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.getByText('line([3.79, 2.68], %, $seg01)').click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
||||||
|
{ timeout: 10_000 }
|
||||||
|
)
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
const lineBefore = await u.getSegmentBodyCoords(
|
||||||
|
`[data-overlay-index="1"]`,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
await u.getGreatestPixDiff(lineBefore, TEST_COLORS.WHITE)
|
||||||
|
).toBeLessThan(3)
|
||||||
|
await page.mouse.move(lineBefore.x, lineBefore.y)
|
||||||
|
await page.waitForTimeout(50)
|
||||||
|
await page.mouse.click(lineBefore.x, lineBefore.y)
|
||||||
|
expect(
|
||||||
|
await u.getGreatestPixDiff(lineBefore, TEST_COLORS.BLUE)
|
||||||
|
).toBeLessThan(3)
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page.getByRole('button', { name: 'Horizontal', exact: true }).click()
|
||||||
|
|
||||||
|
let activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await expect(activeLinesContent[0]).toHaveText(`|> xLine(3.13, %)`)
|
||||||
|
|
||||||
|
// If the overlay-angle is updated the THREE.js scene is in a good state
|
||||||
|
await expect(
|
||||||
|
await page.locator('[data-overlay-index="1"]')
|
||||||
|
).toHaveAttribute('data-overlay-angle', '0')
|
||||||
|
|
||||||
|
const lineAfter = await u.getSegmentBodyCoords(
|
||||||
|
`[data-overlay-index="1"]`,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
await u.getGreatestPixDiff(lineAfter, TEST_COLORS.BLUE)
|
||||||
|
).toBeLessThan(3)
|
||||||
|
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
// await expect(page.getByRole('button', { name: 'length', exact: true })).toBeVisible()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// await page.getByRole('button', { name: 'length', exact: true }).click()
|
||||||
|
await page.getByTestId('dropdown-constraint-length').click()
|
||||||
|
|
||||||
|
await page.getByLabel('length Value').fill('10')
|
||||||
|
await page.getByRole('button', { name: 'Add constraining value' }).click()
|
||||||
|
|
||||||
|
activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
||||||
|
|
||||||
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||||
|
})
|
||||||
|
})
|
249
e2e/playwright/testing-gizmo.spec.ts
Normal file
249
e2e/playwright/testing-gizmo.spec.ts
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
import { TEST_CODE_GIZMO } from './storageStates'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Testing Gizmo', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
testDescription: 'top view',
|
||||||
|
clickPosition: { x: 951, y: 385 },
|
||||||
|
expectedCameraPosition: { x: 800, y: -152, z: 4886.02 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: 'bottom view',
|
||||||
|
clickPosition: { x: 951, y: 429 },
|
||||||
|
expectedCameraPosition: { x: 800, y: -152, z: -4834.02 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: 'right view',
|
||||||
|
clickPosition: { x: 929, y: 417 },
|
||||||
|
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: 'left view',
|
||||||
|
clickPosition: { x: 974, y: 397 },
|
||||||
|
expectedCameraPosition: { x: -4060.02, y: -152, z: 26 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: 'back view',
|
||||||
|
clickPosition: { x: 967, y: 421 },
|
||||||
|
expectedCameraPosition: { x: 800, y: 4708.02, z: 26 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testDescription: 'front view',
|
||||||
|
clickPosition: { x: 935, y: 393 },
|
||||||
|
expectedCameraPosition: { x: 800, y: -5012.02, z: 26 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const {
|
||||||
|
clickPosition,
|
||||||
|
expectedCameraPosition,
|
||||||
|
expectedCameraTarget,
|
||||||
|
testDescription,
|
||||||
|
} of cases) {
|
||||||
|
test(`check ${testDescription}`, async ({ page, browserName }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript((TEST_CODE_GIZMO) => {
|
||||||
|
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
||||||
|
}, TEST_CODE_GIZMO)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
vantage: {
|
||||||
|
x: 3000,
|
||||||
|
y: 3000,
|
||||||
|
z: 3000,
|
||||||
|
},
|
||||||
|
center: {
|
||||||
|
x: 800,
|
||||||
|
y: -152,
|
||||||
|
z: 26,
|
||||||
|
},
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await u.waitForCmdReceive('default_camera_get_settings')
|
||||||
|
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.mouse.move(clickPosition.x, clickPosition.y)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(clickPosition.x, clickPosition.y)
|
||||||
|
await page.mouse.move(0, 0)
|
||||||
|
await u.waitForCmdReceive('default_camera_look_at')
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await u.waitForCmdReceive('default_camera_get_settings')
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
// position
|
||||||
|
expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||||
|
expectedCameraPosition.x.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||||
|
expectedCameraPosition.y.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||||
|
expectedCameraPosition.z.toString()
|
||||||
|
),
|
||||||
|
// target
|
||||||
|
expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||||
|
expectedCameraTarget.x.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||||
|
expectedCameraTarget.y.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||||
|
expectedCameraTarget.z.toString()
|
||||||
|
),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Context menu and popover menu', async ({ page }) => {
|
||||||
|
const testCase = {
|
||||||
|
testDescription: 'Right view',
|
||||||
|
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
|
||||||
|
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test prelude taken from the above test
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript((TEST_CODE_GIZMO) => {
|
||||||
|
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
||||||
|
}, TEST_CODE_GIZMO)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
vantage: {
|
||||||
|
x: 3000,
|
||||||
|
y: 3000,
|
||||||
|
z: 3000,
|
||||||
|
},
|
||||||
|
center: {
|
||||||
|
x: 800,
|
||||||
|
y: -152,
|
||||||
|
z: 26,
|
||||||
|
},
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await u.waitForCmdReceive('default_camera_get_settings')
|
||||||
|
|
||||||
|
// Now find and select the correct
|
||||||
|
// view from the context menu
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
const gizmo = page.locator('[aria-label*=gizmo]')
|
||||||
|
await gizmo.click({ button: 'right' })
|
||||||
|
const buttonToTest = page.getByRole('button', {
|
||||||
|
name: testCase.testDescription,
|
||||||
|
})
|
||||||
|
await expect(buttonToTest).toBeVisible()
|
||||||
|
await buttonToTest.click()
|
||||||
|
|
||||||
|
// Now assert we've moved to the correct view
|
||||||
|
// Taken from the above test
|
||||||
|
await u.waitForCmdReceive('default_camera_look_at')
|
||||||
|
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await u.waitForCmdReceive('default_camera_get_settings')
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
// position
|
||||||
|
expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||||
|
testCase.expectedCameraPosition.x.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||||
|
testCase.expectedCameraPosition.y.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||||
|
testCase.expectedCameraPosition.z.toString()
|
||||||
|
),
|
||||||
|
// target
|
||||||
|
expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||||
|
testCase.expectedCameraTarget.x.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||||
|
testCase.expectedCameraTarget.y.toString()
|
||||||
|
),
|
||||||
|
expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||||
|
testCase.expectedCameraTarget.z.toString()
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
// Now test the popover menu.
|
||||||
|
// It has the same click handlers, so we can just
|
||||||
|
// test that it opens and contains the same content.
|
||||||
|
const gizmoPopoverButton = page.getByRole('button', {
|
||||||
|
name: 'view settings',
|
||||||
|
})
|
||||||
|
await expect(gizmoPopoverButton).toBeVisible()
|
||||||
|
await gizmoPopoverButton.click()
|
||||||
|
await expect(buttonToTest).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
1277
e2e/playwright/testing-segment-overlays.spec.ts
Normal file
1277
e2e/playwright/testing-segment-overlays.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
1005
e2e/playwright/testing-selections.spec.ts
Normal file
1005
e2e/playwright/testing-selections.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
231
e2e/playwright/testing-settings.spec.ts
Normal file
231
e2e/playwright/testing-settings.spec.ts
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { getUtils, setup, tearDown } from './test-utils'
|
||||||
|
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||||
|
import { TEST_SETTINGS_KEY, TEST_SETTINGS_CORRUPTED } from './storageStates'
|
||||||
|
import * as TOML from '@iarna/toml'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Testing settings', () => {
|
||||||
|
test('Stored settings are validated and fall back to defaults', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Override beforeEach test setup
|
||||||
|
// with corrupted settings
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({ settings: TEST_SETTINGS_CORRUPTED }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// Check the settings were reset
|
||||||
|
const storedSettings = TOML.parse(
|
||||||
|
await page.evaluate(
|
||||||
|
({ settingsKey }) => localStorage.getItem(settingsKey) || '',
|
||||||
|
{ settingsKey: TEST_SETTINGS_KEY }
|
||||||
|
)
|
||||||
|
) as { settings: SaveSettingsPayload }
|
||||||
|
|
||||||
|
expect(storedSettings.settings?.app?.theme).toBe(undefined)
|
||||||
|
|
||||||
|
// Check that the invalid settings were removed
|
||||||
|
expect(storedSettings.settings?.modeling?.defaultUnit).toBe(undefined)
|
||||||
|
expect(storedSettings.settings?.modeling?.mouseControls).toBe(undefined)
|
||||||
|
expect(storedSettings.settings?.app?.projectDirectory).toBe(undefined)
|
||||||
|
expect(storedSettings.settings?.projects?.defaultProjectName).toBe(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Project settings can be set and override user settings', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.waitFor({ state: 'visible' })
|
||||||
|
|
||||||
|
// Open the settings modal with the browser keyboard shortcut
|
||||||
|
await page.keyboard.press('Meta+Shift+,')
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||||
|
).toBeVisible()
|
||||||
|
await page
|
||||||
|
.locator('select[name="app-theme"]')
|
||||||
|
.selectOption({ value: 'light' })
|
||||||
|
|
||||||
|
// Verify the toast appeared
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Set theme to "light" for this project`)
|
||||||
|
).toBeVisible()
|
||||||
|
// Check that the theme changed
|
||||||
|
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||||
|
|
||||||
|
// Check that the user setting was not changed
|
||||||
|
await page.getByRole('radio', { name: 'User' }).click()
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
|
||||||
|
|
||||||
|
// Roll back to default "system" theme
|
||||||
|
await page
|
||||||
|
.getByText(
|
||||||
|
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
|
||||||
|
)
|
||||||
|
.hover()
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Roll back theme',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||||
|
|
||||||
|
// Check that the project setting did not change
|
||||||
|
await page.getByRole('radio', { name: 'Project' }).click()
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Project settings can be opened with keybinding from the editor', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.waitFor({ state: 'visible' })
|
||||||
|
|
||||||
|
// Put the cursor in the editor
|
||||||
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
|
// Open the settings modal with the browser keyboard shortcut
|
||||||
|
await page.keyboard.press('Meta+Shift+,')
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||||
|
).toBeVisible()
|
||||||
|
await page
|
||||||
|
.locator('select[name="app-theme"]')
|
||||||
|
.selectOption({ value: 'light' })
|
||||||
|
|
||||||
|
// Verify the toast appeared
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Set theme to "light" for this project`)
|
||||||
|
).toBeVisible()
|
||||||
|
// Check that the theme changed
|
||||||
|
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||||
|
|
||||||
|
// Check that the user setting was not changed
|
||||||
|
await page.getByRole('radio', { name: 'User' }).click()
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
|
||||||
|
|
||||||
|
// Roll back to default "system" theme
|
||||||
|
await page
|
||||||
|
.getByText(
|
||||||
|
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
|
||||||
|
)
|
||||||
|
.hover()
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Roll back theme',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||||
|
|
||||||
|
// Check that the project setting did not change
|
||||||
|
await page.getByRole('radio', { name: 'Project' }).click()
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Project and user settings can be reset', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.waitFor({ state: 'visible' })
|
||||||
|
|
||||||
|
// Put the cursor in the editor
|
||||||
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
|
// Open the settings modal with the browser keyboard shortcut
|
||||||
|
await page.keyboard.press('Meta+Shift+,')
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// Click the reset settings button.
|
||||||
|
await page.getByRole('button', { name: 'Restore default settings' }).click()
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator('select[name="app-theme"]')
|
||||||
|
.selectOption({ value: 'light' })
|
||||||
|
|
||||||
|
// Verify the toast appeared
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Set theme to "light" for this project`)
|
||||||
|
).toBeVisible()
|
||||||
|
// Check that the theme changed
|
||||||
|
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||||
|
|
||||||
|
// Check that the user setting was not changed
|
||||||
|
await page.getByRole('radio', { name: 'User' }).click()
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||||
|
|
||||||
|
// Click the reset settings button.
|
||||||
|
await page.getByRole('button', { name: 'Restore default settings' }).click()
|
||||||
|
|
||||||
|
// Verify it is now set to the default value
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||||
|
|
||||||
|
// Set the user theme to light.
|
||||||
|
await page
|
||||||
|
.locator('select[name="app-theme"]')
|
||||||
|
.selectOption({ value: 'light' })
|
||||||
|
|
||||||
|
// Verify the toast appeared
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Set theme to "light" as a user default`)
|
||||||
|
).toBeVisible()
|
||||||
|
// Check that the theme changed
|
||||||
|
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||||
|
|
||||||
|
await page.getByRole('radio', { name: 'Project' }).click()
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
|
||||||
|
|
||||||
|
// Click the reset settings button.
|
||||||
|
await page.getByRole('button', { name: 'Restore default settings' }).click()
|
||||||
|
// Verify it is now set to the default value
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||||
|
|
||||||
|
await page.getByRole('radio', { name: 'User' }).click()
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||||
|
|
||||||
|
// Click the reset settings button.
|
||||||
|
await page.getByRole('button', { name: 'Restore default settings' }).click()
|
||||||
|
|
||||||
|
// Verify it is now set to the default value
|
||||||
|
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
|
||||||
|
})
|
||||||
|
})
|
569
e2e/playwright/various.spec.ts
Normal file
569
e2e/playwright/various.spec.ts
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import {
|
||||||
|
doExport,
|
||||||
|
getUtils,
|
||||||
|
makeTemplate,
|
||||||
|
metaModifier,
|
||||||
|
setup,
|
||||||
|
tearDown,
|
||||||
|
} from './test-utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Units menu', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const unitsMenuButton = page.getByRole('button', {
|
||||||
|
name: 'Current Units',
|
||||||
|
exact: false,
|
||||||
|
})
|
||||||
|
await expect(unitsMenuButton).toBeVisible()
|
||||||
|
await expect(unitsMenuButton).toContainText('in')
|
||||||
|
|
||||||
|
await unitsMenuButton.click()
|
||||||
|
const millimetersButton = page.getByRole('button', { name: 'Millimeters' })
|
||||||
|
|
||||||
|
await expect(millimetersButton).toBeVisible()
|
||||||
|
await millimetersButton.click()
|
||||||
|
|
||||||
|
// Look out for the toast message
|
||||||
|
const toastMessage = page.getByText(
|
||||||
|
`Set default unit to "mm" for this project`
|
||||||
|
)
|
||||||
|
await expect(toastMessage).toBeVisible()
|
||||||
|
|
||||||
|
// Verify that the popover has closed
|
||||||
|
await expect(millimetersButton).not.toBeAttached()
|
||||||
|
|
||||||
|
// Verify that the button label has updated
|
||||||
|
await expect(unitsMenuButton).toContainText('mm')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Successful export shows a success toast', async ({ page }) => {
|
||||||
|
// FYI this test doesn't work with only engine running locally
|
||||||
|
// And you will need to have the KittyCAD CLI installed
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
;(window as any).playwrightSkipFilePicker = true
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const topAng = 25
|
||||||
|
const bottomAng = 35
|
||||||
|
const baseLen = 3.5
|
||||||
|
const baseHeight = 1
|
||||||
|
const totalHeightHalf = 2
|
||||||
|
const armThick = 0.5
|
||||||
|
const totalLen = 9.5
|
||||||
|
const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> yLine(baseHeight, %)
|
||||||
|
|> xLine(baseLen, %)
|
||||||
|
|> angledLineToY({
|
||||||
|
angle: topAng,
|
||||||
|
to: totalHeightHalf,
|
||||||
|
}, %, $seg04)
|
||||||
|
|> xLineTo(totalLen, %, $seg03)
|
||||||
|
|> yLine(-armThick, %, $seg01)
|
||||||
|
|> angledLineThatIntersects({
|
||||||
|
angle: HALF_TURN,
|
||||||
|
offset: -armThick,
|
||||||
|
intersectTag: seg04
|
||||||
|
}, %)
|
||||||
|
|> angledLineToY([segAng(seg04) + 180, ZERO], %)
|
||||||
|
|> angledLineToY({
|
||||||
|
angle: -bottomAng,
|
||||||
|
to: -totalHeightHalf - armThick,
|
||||||
|
}, %, $seg02)
|
||||||
|
|> xLineTo(segEndX(seg03) + 0, %)
|
||||||
|
|> yLine(-segLen(seg01), %)
|
||||||
|
|> angledLineThatIntersects({
|
||||||
|
angle: HALF_TURN,
|
||||||
|
offset: -armThick,
|
||||||
|
intersectTag: seg02
|
||||||
|
}, %)
|
||||||
|
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
|
||||||
|
|> xLineTo(ZERO, %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(4, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.waitForCmdReceive('extrude')
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await doExport(
|
||||||
|
{
|
||||||
|
type: 'gltf',
|
||||||
|
storage: 'embedded',
|
||||||
|
presentation: 'pretty',
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is the main thing we're testing,
|
||||||
|
// We test the export functionality across all
|
||||||
|
// file types in snapshot-tests.spec.ts
|
||||||
|
await expect(page.getByText('Exported successfully')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Paste should not work unless an input is focused', async ({
|
||||||
|
page,
|
||||||
|
browserName,
|
||||||
|
}) => {
|
||||||
|
// To run this test locally, uncomment Firefox in playwright.config.ts
|
||||||
|
test.skip(
|
||||||
|
browserName !== 'firefox',
|
||||||
|
"This bug is really Firefox-only, which we don't run in CI."
|
||||||
|
)
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.waitFor({ state: 'visible' })
|
||||||
|
|
||||||
|
const codeEditorText = page.locator('.cm-content')
|
||||||
|
const pasteContent = `// was this pasted?`
|
||||||
|
const typeContent = `// this should be typed`
|
||||||
|
|
||||||
|
// Load text into the clipboard
|
||||||
|
await page.evaluate((t) => navigator.clipboard.writeText(t), pasteContent)
|
||||||
|
|
||||||
|
// Focus the text editor
|
||||||
|
await codeEditorText.focus()
|
||||||
|
|
||||||
|
// Show that we can type into it
|
||||||
|
await page.keyboard.type(typeContent)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// Paste without the code pane focused
|
||||||
|
await codeEditorText.blur()
|
||||||
|
await page.keyboard.press(`${metaModifier}+KeyV`)
|
||||||
|
|
||||||
|
// Show that the paste didn't work but typing did
|
||||||
|
await expect(codeEditorText).not.toContainText(pasteContent)
|
||||||
|
await expect(codeEditorText).toContainText(typeContent)
|
||||||
|
|
||||||
|
// Paste with the code editor focused
|
||||||
|
// Following this guidance: https://github.com/microsoft/playwright/issues/8114
|
||||||
|
await codeEditorText.focus()
|
||||||
|
await page.keyboard.press(`${metaModifier}+KeyV`)
|
||||||
|
await expect(
|
||||||
|
await page.evaluate(
|
||||||
|
() => document.querySelector('.cm-content')?.textContent
|
||||||
|
)
|
||||||
|
).toContain(pasteContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Keyboard shortcuts can be viewed through the help menu', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.waitFor({ state: 'visible' })
|
||||||
|
|
||||||
|
// Open the help menu
|
||||||
|
await page.getByRole('button', { name: 'Help and resources' }).click()
|
||||||
|
|
||||||
|
// Open the keyboard shortcuts
|
||||||
|
await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click()
|
||||||
|
|
||||||
|
// Verify the URL and that you can see a list of shortcuts
|
||||||
|
await expect(page.url()).toContain('?tab=keybindings')
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Enter Sketch Mode' })
|
||||||
|
).toBeAttached()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('First escape in tool pops you out of tool, second exits sketch mode', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Wait for the app to be ready for use
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
const lineButton = page.getByRole('button', { name: 'Line', exact: true })
|
||||||
|
const arcButton = page.getByRole('button', {
|
||||||
|
name: 'Tangential Arc',
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test these hotkeys perform actions when
|
||||||
|
// focus is on the canvas
|
||||||
|
await page.mouse.move(600, 250)
|
||||||
|
await page.mouse.click(600, 250)
|
||||||
|
|
||||||
|
// Start a sketch
|
||||||
|
await page.keyboard.press('s')
|
||||||
|
await page.mouse.move(800, 300)
|
||||||
|
await page.mouse.click(800, 300)
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await expect(lineButton).toBeVisible()
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
|
||||||
|
// Draw a line
|
||||||
|
await page.mouse.move(700, 200, { steps: 5 })
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
await page.mouse.move(800, 250, { steps: 5 })
|
||||||
|
await page.mouse.click(800, 250)
|
||||||
|
// Unequip line tool
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
// Make sure we didn't pop out of sketch mode.
|
||||||
|
await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible()
|
||||||
|
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
|
||||||
|
// Equip arc tool
|
||||||
|
await page.keyboard.press('a')
|
||||||
|
await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
await page.mouse.move(1000, 100, { steps: 5 })
|
||||||
|
await page.mouse.click(1000, 100)
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await page.keyboard.press('l')
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
|
||||||
|
// Do not close the sketch.
|
||||||
|
// On close it will exit sketch mode.
|
||||||
|
|
||||||
|
// Unequip line tool
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
|
||||||
|
await expect(arcButton).toHaveAttribute('aria-pressed', 'false')
|
||||||
|
// Make sure we didn't pop out of sketch mode.
|
||||||
|
await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible()
|
||||||
|
// Exit sketch
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
||||||
|
// This test can run long if it takes a little too long to load
|
||||||
|
// the engine.
|
||||||
|
test.setTimeout(90000)
|
||||||
|
// This test has a weird bug on ubuntu
|
||||||
|
test.skip(
|
||||||
|
process.platform === 'linux',
|
||||||
|
'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444'
|
||||||
|
)
|
||||||
|
// Load the app with the code pane open
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'store',
|
||||||
|
JSON.stringify({
|
||||||
|
state: {
|
||||||
|
openPanes: ['code'],
|
||||||
|
},
|
||||||
|
version: 0,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wait for the app to be ready for use
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
const codePane = page.getByRole('textbox').locator('div')
|
||||||
|
const codePaneButton = page.getByTestId('code-pane-button')
|
||||||
|
const lineButton = page.getByRole('button', { name: 'Line', exact: true })
|
||||||
|
const arcButton = page.getByRole('button', {
|
||||||
|
name: 'Tangential Arc',
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
|
||||||
|
|
||||||
|
// Test that the hotkeys do nothing when
|
||||||
|
// focus is on the code pane
|
||||||
|
await codePane.click()
|
||||||
|
await page.keyboard.press('/')
|
||||||
|
await page.keyboard.press('/')
|
||||||
|
await page.keyboard.press('s')
|
||||||
|
await page.keyboard.press('l')
|
||||||
|
await page.keyboard.press('a')
|
||||||
|
await page.keyboard.press('e')
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText('//slae')
|
||||||
|
await page.keyboard.press('Meta+/')
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
// Test these hotkeys perform actions when
|
||||||
|
// focus is on the canvas
|
||||||
|
await page.mouse.move(600, 250)
|
||||||
|
await page.mouse.click(600, 250)
|
||||||
|
|
||||||
|
// work-around: to stop "keyboard.press('s')" from typing in the editor even when it should be blurred
|
||||||
|
await page.getByRole('button', { name: 'Commands' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
// end work-around
|
||||||
|
|
||||||
|
// Start a sketch
|
||||||
|
await page.keyboard.press('s')
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await page.mouse.move(800, 300, { steps: 5 })
|
||||||
|
await page.mouse.click(800, 300)
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
||||||
|
timeout: 15_000,
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* TODO: There is a bug somewhere that causes this test to fail
|
||||||
|
* if you toggle the codePane closed before your trigger the
|
||||||
|
* start of the sketch.
|
||||||
|
* and a separate Safari-only bug that causes the test to fail
|
||||||
|
* if the pane is open the entire test. The maintainer of CodeMirror
|
||||||
|
* has pinpointed this to the unusual browser behavior:
|
||||||
|
* https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3
|
||||||
|
*/
|
||||||
|
await codePaneButton.click()
|
||||||
|
await expect(u.codeLocator).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
|
// Draw a line
|
||||||
|
await page.mouse.move(700, 200, { steps: 5 })
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
await page.mouse.move(800, 250, { steps: 5 })
|
||||||
|
await page.mouse.click(800, 250)
|
||||||
|
// Unequip line tool
|
||||||
|
await page.keyboard.press('l')
|
||||||
|
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
|
||||||
|
// Equip arc tool
|
||||||
|
await page.keyboard.press('a')
|
||||||
|
await expect(arcButton).toHaveAttribute('aria-pressed', 'true', {
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
await page.mouse.move(1000, 100, { steps: 5 })
|
||||||
|
await page.mouse.click(1000, 100)
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await page.keyboard.press('l')
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
// Close profile
|
||||||
|
await page.mouse.move(700, 200, { steps: 5 })
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
// On close it will unequip the line tool.
|
||||||
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
|
||||||
|
// Exit sketch
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
|
).not.toBeVisible()
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
|
||||||
|
// Extrude
|
||||||
|
await page.mouse.click(750, 150)
|
||||||
|
await expect(extrudeButton).not.toBeDisabled()
|
||||||
|
await page.keyboard.press('e')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.move(800, 200, { steps: 5 })
|
||||||
|
await page.mouse.click(800, 200)
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click()
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Submit command' })
|
||||||
|
).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||||
|
|
||||||
|
await codePaneButton.click()
|
||||||
|
await expect(page.locator('.cm-content')).toContainText('extrude(')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Delete key does not navigate back', async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
|
const settingsButton = page.getByRole('link', {
|
||||||
|
name: 'Settings',
|
||||||
|
exact: false,
|
||||||
|
})
|
||||||
|
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||||
|
|
||||||
|
await settingsButton.click()
|
||||||
|
await expect(page.url()).toContain('/settings')
|
||||||
|
|
||||||
|
// Make sure that delete doesn't go back from settings
|
||||||
|
await page.keyboard.press('Delete')
|
||||||
|
await expect(page.url()).toContain('/settings')
|
||||||
|
|
||||||
|
// Now close the settings and try delete again,
|
||||||
|
// make sure it doesn't go back to settings
|
||||||
|
await settingsCloseButton.click()
|
||||||
|
await page.keyboard.press('Delete')
|
||||||
|
await expect(page.url()).not.toContain('/settings')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Sketch on face', async ({ page }) => {
|
||||||
|
test.setTimeout(90_000)
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([3.29, 7.86], %)
|
||||||
|
|> line([2.48, 2.44], %)
|
||||||
|
|> line([2.66, 1.17], %)
|
||||||
|
|> line([3.75, 0.46], %)
|
||||||
|
|> line([4.99, -0.46], %)
|
||||||
|
|> line([3.3, -2.12], %)
|
||||||
|
|> line([2.16, -3.33], %)
|
||||||
|
|> line([0.85, -3.08], %)
|
||||||
|
|> line([-0.18, -3.36], %)
|
||||||
|
|> line([-3.86, -2.73], %)
|
||||||
|
|> line([-17.67, 0.85], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(5 + 7, sketch001)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
|
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await u.doAndWaitForCmd(
|
||||||
|
() => page.mouse.click(625, 133),
|
||||||
|
'default_camera_get_settings',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
await page.waitForTimeout(150)
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
const firstClickPosition = [612, 238]
|
||||||
|
const secondClickPosition = [661, 242]
|
||||||
|
const thirdClickPosition = [609, 267]
|
||||||
|
|
||||||
|
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toContainText(`const sketch002 = startSketchOn(extrude001, seg01)
|
||||||
|
|> startProfileAt([-12.94, 6.6], %)
|
||||||
|
|> line([2.45, -0.2], %)
|
||||||
|
|> line([-2.6, -1.25], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
|
await u.updateCamPosition([1049, 239, 686])
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.getByText('startProfileAt([-12.94, 6.6], %)').click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
await page.waitForTimeout(150)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 1200 })
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await u.updateCamPosition([452, -152, 1166])
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
const pointToDragFirst = [787, 565]
|
||||||
|
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
|
||||||
|
await page.mouse.down()
|
||||||
|
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
|
||||||
|
steps: 5,
|
||||||
|
})
|
||||||
|
await page.mouse.up()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
const result = makeTemplate`const sketch002 = startSketchOn(extrude001, seg01)
|
||||||
|
|> startProfileAt([-12.83, 6.7], %)
|
||||||
|
|> line([${[2.28, 2.35]}, -${0.07}], %)
|
||||||
|
|> line([-3.05, -1.47], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)`
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(result.regExp)
|
||||||
|
|
||||||
|
// exit sketch
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
|
await page.getByText('startProfileAt([-12.94, 6.6], %)').click()
|
||||||
|
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||||
|
|
||||||
|
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
const result2 = result.genNext`
|
||||||
|
const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
||||||
|
})
|
Reference in New Issue
Block a user