* updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
* updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
* updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
* make tsc happ
* better error msg
* fix control point issue
* basic code gen working for tangentialArc
* partical fix for move with arcs
* tangential arc move
* fix
* make eslint rules less annoying
* inital refactor of some xstate stuff
* more old tangential arc clean up stuff
* more tweaks
* add testing
* tweak xstate inspect
* temp remove test
* update formating for less conflicts
* fix state machine layout after merge
* shrug, something weird with xstate typegen
* renaming some xstate events
* tweak numbers to make CI playwright happ
* CI hacks
* more CI hacks
* more CI hacks
* new hack strategy
* run tests agian
* make cmd bar less flaky
* ci hacks
* CI hacks
* CI hacks
* CI hacks
* clean up
* fix
* still have constraint stuff to deal with
* progress on move rules
* update source ranges after no execute code-mod
* typo
* mvp working
* hide show sketch overlay
* match scaling
* update arrow head style
* animate line tool
* bypass xstate for animations, much smoother
* add new segment working with refactor needed for setup paper sketch
* refactor setup paper sketch
* tangantialArcTo drag animations working
* tangential arc polish
* cargo fmt
* clippy
* more clippy
* mock canvas
* last of clippy?
* typo
* more clippy stuff
* move util function so they are shareable with typescript
* migrate a bunch to rust and only rust
* add arc center point for draft tangential ac
* clippy tweak
* delete uneeded test
* Rough start to scaling arrow heads.
The tangent arrow heads are basically nuked and replaced while the
straight line sections are just rotated and repositioned, this means they
miss out on updating scaling number after a screen size changes.
Needs fixing
* fix bug with tool tips
* fix draft line start position
Having drag the end of teh path before selecting a tooltip would result in the draft line starting where the path used to end, stale data
* some progress with pan maybe
* fmt
* inital camera sync working
For perspective camera at least
* change three.js to use z-up
* add grid
* orthographic camera working with polish items TODO
* fix zoom level when swapping camera
* fix up camera/orbit changing on cam change (pan wasn't being respected)
* tidy up
* use orbit target instead of assuming scene center
* dynamic fov working
* animate orthographic to perspective and reverse
* fix import
* temp fix for batch commands
* initial client side scene sketch working
* remove hover log
* FOV adjust fix
* fix comment
* tear down sketch and small tweaks
* some progress with camera tweening
* combine dollyZoom engine commands
see
https://github.com/KittyCAD/modeling-api/compare/kurt-perspective-settings?expand=1
and
https://github.com/KittyCAD/engine/compare/kurt-perspective-settings?expand=1
* make tests happy (mocks)
* fix tween to vertical/camera-up bug
* tween to each axis with hacky solutions in there
* fix startSketchOn planes
* tidy startSketchOn
* tweening okay for now I think
* get sketching on default planes working
* allow editing on all default planes
* clean up enter and exit sketch logic
* tidy
* tidy
* remove more default plane stuff
* start of draft line
* remove some annoying parts of the paper.js implementation
* fix drag than equip line bug
* comment
* don't animate on skech tear down since it's used for draft line
* remove more default plane shit
* style draft line
* refine dashed line
* draft line set up and tear down mostly happy
* add on click logic ready for draft lines
* sketch mode with drag and draft mode working solidly now, straight segments only
* default planes match colors, hover and select still TODO
* hover and click logic working for default planes
Now just need the code mode to fire to 'startSketchOn(...)'
* select default planes
* remove some logs
* fix update infinite loop
* start of orbitControls port to Franks control guards
* hiding scenes at different times
* scene hide on camera move should be respected by scroll zoom
* basic hover working
* Hook up user camera settings to ClientSideScene (#1334)
* Refactor to not import utilities from Router.tsx
* Stop tracking changes or formatting *.typegen.ts
* Hook up cameraControls to ClientSideScene
* Remove camera controls toggle from temp debug panel
---------
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
* add select segment moves cursor
* highlight segments yellow on hover
* cursor ranges effect 2d line colors
* fix constrainst i.e. make sure the sketch is rejiged
* selecting nothing should remove selections
* remove hardcoded strings
* update get_tangential_arc_to_info rust util
* initial drawing of tangential arcs in client scene
* fix tangentialArc arrow head direction
* correct userData types for tangential arcs
* get tangential arc updates working
Doesn't include draging the head of the tangential arc itself yet
* spot of clean up
* make selections work with tangential arcs
* get draft tangential segment animated
* fix initial click weirdness for adding new tangential line
* couple tweaks
* add grace pixels /threshold to raycast
* redo arc dashes so that they spawn from the ccenter of the arc
* fix multi drag bug
* fmt
* add temp solution for close
* add default axis hover colors, still needs select logic
* selection of axis works, just with out selection color
* get axis selection colors working
* fix outdate source ranges after drag problem
* update moreNodePathFromSourceRange
* fix ts-rs issue/workaround
* fix default plane weirdness
* fix tangential arc rounding issue
* review clean up part 1
* review clean up part 2
Big state-diagram cull
* clippy
* typo
* clippy
* fix xstate types with typegen
* fix types
* clippy
* catch error
* fix test import issue
Not sure exactly what was happening but guessing circular import that vite didn't like
* add axis/plane info to sketch group tests
* case changes because of rs-ts bug, can probably revert this later
* start of playwright test fixes
* reduce geo complexity for straight segments
* fix cam adjust tests
* Revert "Clean up vite build warnings (#1332)"
This reverts commit c1f661ab52
.
* selection e2e test fixed<
* remove camToggle to allow playwright tests to pass
* remove drag test
too brittle and needs to be redone from the ground up anyway
* trigger CI
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix last test
* clean up part 3
* clean up part 4
* clean up part 5
* clean up sketch enter exit logic
* fix engine side selections
* default plane should not be selected form 'onDragEnd'
i.e. rotating the camera should not mean the user acidently selects a plane
* clean up state diagram around animating to sketch mode
Embracing that the animation is async and puting the interdiate steps in the state diagram clean up some logic and solved some bugs at the same time
* add test for multiple sketches
* typo
* make highlight more robust
* type tweak
* scale segmenst with distance from camera so they have a consistent pixel size/ screenspace size
* Jess's advice
* tsc and fmt
* clean up part 6
remove integer from xstate names
* clean up part 7
* integrate sequency in to camera moves
* fix tests
* update snapshot e2e
* small snapshot change
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* trigger ci
* Fix HomeLoaderData types
* update std stuff
* update kittycad rs client lib
---------
Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
813 lines
27 KiB
TypeScript
813 lines
27 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { secrets } from './secrets'
|
|
import { getUtils } from './test-utils'
|
|
import waitOn from 'wait-on'
|
|
import { Themes } from '../../src/lib/theme'
|
|
|
|
/*
|
|
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
|
just from the nature of the stream, running the test with debugger and pasting the below
|
|
into the console can be useful to get coords
|
|
|
|
document.addEventListener('mousemove', (e) =>
|
|
console.log(`await page.mouse.click(${e.clientX}, ${e.clientY})`)
|
|
)
|
|
*/
|
|
|
|
test.beforeEach(async ({ context, page }) => {
|
|
// wait for Vite preview server to be up
|
|
await waitOn({
|
|
resources: ['tcp:3000'],
|
|
timeout: 5000,
|
|
})
|
|
await context.addInitScript(async (token) => {
|
|
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
|
localStorage.setItem('persistCode', ``)
|
|
localStorage.setItem(
|
|
'SETTINGS_PERSIST_KEY',
|
|
JSON.stringify({
|
|
baseUnit: 'in',
|
|
cameraControls: 'KittyCAD',
|
|
defaultDirectory: '',
|
|
defaultProjectName: 'project-$nnn',
|
|
onboardingStatus: 'dismissed',
|
|
showDebugPanel: true,
|
|
textWrapping: 'On',
|
|
theme: 'system',
|
|
unitSystem: 'imperial',
|
|
})
|
|
)
|
|
}, secrets.token)
|
|
// kill animations, speeds up tests and reduced flakiness
|
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
|
})
|
|
|
|
test.setTimeout(60000)
|
|
|
|
test('Basic sketch', async ({ page }) => {
|
|
const u = getUtils(page)
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
await page.goto('/')
|
|
await u.waitForAuthSkipAppStart()
|
|
await u.openDebugPanel()
|
|
|
|
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
|
|
)
|
|
|
|
// select a plane
|
|
await page.mouse.click(700, 200)
|
|
|
|
await expect(page.locator('.cm-content')).toHaveText(
|
|
`const part001 = startSketchOn('-XZ')`
|
|
)
|
|
|
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
|
|
|
const startXPx = 600
|
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
const startAt = '[23.89, -32.23]'
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)`)
|
|
await page.waitForTimeout(100)
|
|
|
|
await u.closeDebugPanel()
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
await page.waitForTimeout(100)
|
|
|
|
const num = 24.11
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line([${num}, 0], %)`)
|
|
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line([${num}, 0], %)
|
|
|> line([0, ${num + 0.01}], %)`)
|
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line([${num}, 0], %)
|
|
|> line([0, ${num + 0.01}], %)
|
|
|> line([-48, 0], %)`)
|
|
|
|
// deselect line tool
|
|
await page.getByRole('button', { name: 'Line' }).click()
|
|
await page.waitForTimeout(100)
|
|
|
|
// 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)
|
|
|
|
// 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
|
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
|
|
|
await page.getByRole('button', { name: 'Equal Length' }).click()
|
|
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line({ to: [${num}, 0], tag: 'seg01' }, %)
|
|
|> line([0, ${num + 0.01}], %)
|
|
|> angledLine([180, segLen('seg01', %)], %)`)
|
|
})
|
|
|
|
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
|
const u = getUtils(page)
|
|
await page.setViewportSize({ width: 1000, height: 500 })
|
|
await page.goto('/')
|
|
|
|
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 page.click('.cm-content')
|
|
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("found unknown token '#'")).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 expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
|
await expect(page.locator('.cm-lintRange.cm-lintRange-error')).toBeVisible()
|
|
|
|
await page.locator('.cm-lintRange.cm-lintRange-error').hover()
|
|
await expect(page.locator('.cm-diagnosticText')).toBeVisible()
|
|
await expect(page.getByText('Cannot redefine topAng')).toBeVisible()
|
|
|
|
const secondTopAng = await page.getByText('topAng').first()
|
|
await secondTopAng?.dblclick()
|
|
await page.keyboard.type('otherAng')
|
|
|
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
|
})
|
|
|
|
test('executes on load', async ({ page, context }) => {
|
|
const u = getUtils(page)
|
|
await context.addInitScript(async (token) => {
|
|
localStorage.setItem(
|
|
'persistCode',
|
|
`const part001 = 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 page.goto('/')
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
// expand variables section
|
|
await page.getByText('Variables').click()
|
|
|
|
// can find part001 in the variables summary (pretty-json-container, makes sure we're not looking in the code editor)
|
|
// part001 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('part001')
|
|
})
|
|
await expect(
|
|
page.locator('.pretty-json-container >> text=part001')
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('re-executes', async ({ page, context }) => {
|
|
const u = getUtils(page)
|
|
await context.addInitScript(async (token) => {
|
|
localStorage.setItem('persistCode', `const myVar = 5`)
|
|
})
|
|
await page.setViewportSize({ width: 1000, height: 500 })
|
|
await page.goto('/')
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
await page.getByText('Variables').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('Can create sketches on all planes and their back sides', async ({
|
|
page,
|
|
}) => {
|
|
const u = getUtils(page)
|
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
await page.goto('/')
|
|
await u.waitForAuthSkipAppStart()
|
|
await u.openDebugPanel()
|
|
|
|
const camPos: [number, number, number] = [100, 100, 100]
|
|
|
|
const TestSinglePlane = async ({
|
|
viewCmd,
|
|
expectedCode,
|
|
clickCoords,
|
|
}: {
|
|
viewCmd: [number, number, number]
|
|
expectedCode: string
|
|
clickCoords: { x: number; y: number }
|
|
}) => {
|
|
await u.openDebugPanel()
|
|
|
|
await u.updateCamPosition(viewCmd)
|
|
|
|
await u.clearCommandLogs()
|
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
|
|
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' })).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(expectedCode)
|
|
|
|
await page.getByRole('button', { name: 'Line' }).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()
|
|
}
|
|
|
|
const codeTemplate = (
|
|
plane = 'XY',
|
|
rounded = false
|
|
) => `const part001 = startSketchOn('${plane}')
|
|
|> startProfileAt([28.91, -39${rounded ? '' : '.01'}], %)`
|
|
await TestSinglePlane({
|
|
viewCmd: camPos,
|
|
expectedCode: codeTemplate('XY'),
|
|
clickCoords: { x: 600, y: 388 }, // red plane
|
|
// clickCoords: { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
|
|
})
|
|
await TestSinglePlane({
|
|
viewCmd: camPos,
|
|
expectedCode: codeTemplate('YZ', true),
|
|
clickCoords: { x: 700, y: 300 }, // green plane
|
|
})
|
|
await TestSinglePlane({
|
|
viewCmd: camPos,
|
|
expectedCode: codeTemplate('XZ'),
|
|
clickCoords: { x: 700, y: 80 }, // blue plane
|
|
})
|
|
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
|
|
await TestSinglePlane({
|
|
viewCmd: camCmdBackSide,
|
|
expectedCode: codeTemplate('-XY', true),
|
|
clickCoords: { x: 601, y: 118 }, // back of red plane
|
|
})
|
|
await TestSinglePlane({
|
|
viewCmd: camCmdBackSide,
|
|
expectedCode: codeTemplate('-YZ'),
|
|
clickCoords: { x: 730, y: 219 }, // back of green plane
|
|
})
|
|
await TestSinglePlane({
|
|
viewCmd: camCmdBackSide,
|
|
expectedCode: codeTemplate('-XZ', true),
|
|
clickCoords: { x: 680, y: 427 }, // back of blue plane
|
|
})
|
|
})
|
|
|
|
test('Auto complete works', async ({ page }) => {
|
|
const u = getUtils(page)
|
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
await page.goto('/')
|
|
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 page.click('.cm-content')
|
|
await page.keyboard.type('const part001 = start')
|
|
|
|
// expect there to be three auto complete options
|
|
await expect(page.locator('.cm-completionLabel')).toHaveCount(3)
|
|
await page.getByText('startSketchOn').click()
|
|
await page.keyboard.type("('XY')")
|
|
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.keyboard.press('Enter') // accepting the auto complete, not a new line
|
|
|
|
await page.keyboard.type('([0,0], %)')
|
|
await page.keyboard.press('Enter')
|
|
await page.keyboard.type(' |> lin')
|
|
|
|
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible()
|
|
// press arrow down twice then enter to accept xLine
|
|
await page.keyboard.press('ArrowDown')
|
|
await page.keyboard.press('ArrowDown')
|
|
await page.keyboard.press('Enter')
|
|
await page.keyboard.type('(5, %)')
|
|
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('XY')
|
|
|> startProfileAt([0,0], %)
|
|
|> xLine(5, %)`)
|
|
})
|
|
|
|
// Onboarding tests
|
|
test('Onboarding redirects and code updating', async ({ page, context }) => {
|
|
const u = getUtils(page)
|
|
|
|
// Override beforeEach test setup
|
|
await context.addInitScript(async () => {
|
|
// Give some initial code, so we can test that it's cleared
|
|
localStorage.setItem('persistCode', 'const sigmaAllow = 15000')
|
|
|
|
const storedSettings = JSON.parse(
|
|
localStorage.getItem('SETTINGS_PERSIST_KEY') || '{}'
|
|
)
|
|
storedSettings.onboardingStatus = '/export'
|
|
localStorage.setItem('SETTINGS_PERSIST_KEY', JSON.stringify(storedSettings))
|
|
})
|
|
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
await page.goto('/')
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
// Test that the redirect happened
|
|
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
|
`/file/new/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/new/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('Selections work on fresh and edited sketch', async ({ page }) => {
|
|
// tests mapping works on fresh sketch and edited sketch
|
|
// tests using hovers which is the same as selections, because if
|
|
// source ranges are wrong, hovers won't work
|
|
const u = getUtils(page)
|
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
await page.goto('/')
|
|
await u.waitForAuthSkipAppStart()
|
|
await u.openDebugPanel()
|
|
|
|
const xAxisClick = () =>
|
|
page.mouse.click(700, 250).then(() => page.waitForTimeout(100))
|
|
const emptySpaceClick = () =>
|
|
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
|
|
const topHorzSegmentClick = () =>
|
|
page.mouse.click(709, 289).then(() => page.waitForTimeout(100))
|
|
const bottomHorzSegmentClick = () =>
|
|
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
|
|
|
|
await u.clearCommandLogs()
|
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
|
|
// select a plane
|
|
await page.mouse.click(700, 200)
|
|
await page.waitForTimeout(700) // wait for animation
|
|
|
|
const startXPx = 600
|
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
const startAt = '[23.89, -32.23]'
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)`)
|
|
|
|
await u.closeDebugPanel()
|
|
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
|
|
const num = 24.11
|
|
const num2 = '48'
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line([${num}, 0], %)`)
|
|
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line([${num}, 0], %)
|
|
|> line([0, ${num + 0.01}], %)`)
|
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line([${num}, 0], %)
|
|
|> line([0, ${num + 0.01}], %)
|
|
|> line([-${num2}, 0], %)`)
|
|
|
|
// deselect line tool
|
|
await page.getByRole('button', { name: 'Line' }).click()
|
|
|
|
await u.closeDebugPanel()
|
|
const selectionSequence = async () => {
|
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
|
|
await page.mouse.move(startXPx + PUR * 15, 500 - PUR * 10)
|
|
|
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
|
// bg-yellow-200 is more brittle than hover-highlight, but is closer to the user experience
|
|
// and will be an easy fix if it breaks because we change the colour
|
|
await expect(page.locator('.bg-yellow-200')).toBeVisible()
|
|
|
|
// check mousing off, than mousing onto another line
|
|
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
|
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 20) // mouse onto another line
|
|
await expect(page.getByTestId('hover-highlight')).toBeVisible()
|
|
|
|
// now check clicking works including axis
|
|
|
|
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
|
await topHorzSegmentClick()
|
|
await page.keyboard.down('Shift')
|
|
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
|
await expect(absYButton).toBeDisabled()
|
|
await xAxisClick()
|
|
await page.keyboard.up('Shift')
|
|
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
|
await expect(absYButton).not.toBeDisabled()
|
|
|
|
// clear selection by clicking on nothing
|
|
await emptySpaceClick()
|
|
|
|
// same selection but click the axis first
|
|
await xAxisClick()
|
|
await expect(absYButton).toBeDisabled()
|
|
await page.keyboard.down('Shift')
|
|
await topHorzSegmentClick()
|
|
|
|
await page.keyboard.up('Shift')
|
|
await expect(absYButton).not.toBeDisabled()
|
|
|
|
// clear selection by clicking on nothing
|
|
await emptySpaceClick()
|
|
|
|
// check the same selection again by putting cursor in code first then selecting axis
|
|
await page.getByText(` |> line([-${num2}, 0], %)`).click()
|
|
await page.keyboard.down('Shift')
|
|
await expect(absYButton).toBeDisabled()
|
|
await xAxisClick()
|
|
await page.keyboard.up('Shift')
|
|
await expect(absYButton).not.toBeDisabled()
|
|
|
|
// clear selection by clicking on nothing
|
|
await emptySpaceClick()
|
|
|
|
// select segment in editor than another segment in scene and check there are two cursors
|
|
await page.getByText(` |> line([-${num2}, 0], %)`).click()
|
|
await page.waitForTimeout(300)
|
|
await page.keyboard.down('Shift')
|
|
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
|
await bottomHorzSegmentClick()
|
|
await page.keyboard.up('Shift')
|
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
|
|
|
// clear selection by clicking on nothing
|
|
await emptySpaceClick()
|
|
}
|
|
|
|
await selectionSequence()
|
|
|
|
// hovering in fresh sketch worked, lets try exiting and re-entering
|
|
await u.openAndClearDebugPanel()
|
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
await page.waitForTimeout(200)
|
|
// wait for execution done
|
|
|
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
await u.closeDebugPanel()
|
|
|
|
// select a line
|
|
await topHorzSegmentClick()
|
|
await page.waitForTimeout(200)
|
|
|
|
// enter sketch again
|
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
await page.waitForTimeout(700) // wait for animation
|
|
|
|
// hover again and check it works
|
|
await selectionSequence()
|
|
})
|
|
|
|
test('Command bar works and can change a setting', async ({ page }) => {
|
|
// Brief boilerplate
|
|
const u = getUtils(page)
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
await page.goto('/')
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
|
|
|
// First try opening the command bar and closing it
|
|
// It has a different label on mac and windows/linux, "Meta+K" and "Ctrl+/" respectively
|
|
await page
|
|
.getByRole('button', { name: 'Ctrl+/' })
|
|
.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 page.keyboard.type('theme')
|
|
const themeOption = page.getByRole('option', { name: 'Set 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 expect(page.getByRole('option', { name: Themes.Dark })).toHaveAttribute(
|
|
'data-headlessui-state',
|
|
'active'
|
|
)
|
|
await page.keyboard.press('Enter')
|
|
|
|
// Check the toast appeared
|
|
await expect(page.getByText(`Set Theme to "${Themes.Dark}"`)).toBeVisible()
|
|
// Check that the theme changed
|
|
await expect(page.locator('body')).toHaveClass(`body-bg ${Themes.Dark}`)
|
|
})
|
|
|
|
test('Can extrude from the command bar', async ({ page, context }) => {
|
|
await context.addInitScript(async (token) => {
|
|
localStorage.setItem(
|
|
'persistCode',
|
|
`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt([-6.95, 4.98], %)
|
|
|> line([25.1, 0.41], %)
|
|
|> line([0.73, -14.93], %)
|
|
|> line([-23.44, 0.52], %)
|
|
|> close(%)`
|
|
)
|
|
})
|
|
|
|
const u = getUtils(page)
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
await page.goto('/')
|
|
await u.waitForAuthSkipAppStart()
|
|
await u.openDebugPanel()
|
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
|
|
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()
|
|
await expect(page.locator('#arg-form > label')).toContainText(
|
|
'Please select one face'
|
|
)
|
|
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
|
|
|
|
// Click to select face and set distance
|
|
await u.openAndClearDebugPanel()
|
|
await page.getByText('|> startProfileAt([-6.95, 4.98], %)').click()
|
|
await u.waitForCmdReceive('select_add')
|
|
await u.closeDebugPanel()
|
|
await page.getByRole('button', { name: 'Continue' }).click()
|
|
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
|
await page.keyboard.press('Enter')
|
|
|
|
// Review step and argument hotkeys
|
|
await page.keyboard.press('2')
|
|
await expect(page.getByRole('button', { name: '5' })).toBeDisabled()
|
|
await page.keyboard.press('Enter')
|
|
|
|
// Check that the code was updated
|
|
await page.keyboard.press('Enter')
|
|
await expect(page.locator('.cm-content')).toHaveText(
|
|
`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt([-6.95, 4.98], %)
|
|
|> line([25.1, 0.41], %)
|
|
|> line([0.73, -14.93], %)
|
|
|> line([-23.44, 0.52], %)
|
|
|> close(%)
|
|
|> extrude(5, %)`
|
|
)
|
|
})
|
|
|
|
test('Can add multiple sketches', async ({ page }) => {
|
|
const u = getUtils(page)
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
await page.goto('/')
|
|
await u.waitForAuthSkipAppStart()
|
|
await u.openDebugPanel()
|
|
|
|
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
|
|
)
|
|
|
|
// select a plane
|
|
await page.mouse.click(700, 200)
|
|
|
|
await expect(page.locator('.cm-content')).toHaveText(
|
|
`const part001 = startSketchOn('-XZ')`
|
|
)
|
|
|
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
|
|
|
const startXPx = 600
|
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
const startAt = '[23.89, -32.23]'
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)`)
|
|
await page.waitForTimeout(100)
|
|
|
|
await u.closeDebugPanel()
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
await page.waitForTimeout(100)
|
|
|
|
const num = 24.11
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line([${num}, 0], %)`)
|
|
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
|
await expect(page.locator('.cm-content'))
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line([${num}, 0], %)
|
|
|> line([0, ${num + 0.01}], %)`)
|
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
|
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|
|
|> startProfileAt(${startAt}, %)
|
|
|> line([${num}, 0], %)
|
|
|> line([0, ${num + 0.01}], %)
|
|
|> line([-48, 0], %)`
|
|
await expect(page.locator('.cm-content')).toHaveText(finalCodeFirstSketch)
|
|
|
|
// exit the sketch
|
|
|
|
await u.openAndClearDebugPanel()
|
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
|
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
|
|
await u.updateCamPosition([0, 100, 100])
|
|
|
|
// start a new sketch
|
|
await u.clearCommandLogs()
|
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
await page.waitForTimeout(100)
|
|
await page.mouse.click(673, 384)
|
|
|
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
|
await u.clearAndCloseDebugPanel()
|
|
|
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
const startAt2 = '[23.61, -31.85]'
|
|
await expect(
|
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
|
).toBe(
|
|
`${finalCodeFirstSketch}
|
|
const part002 = startSketchOn('XY')
|
|
|> startProfileAt(${startAt2}, %)`.replace(/\s/g, '')
|
|
)
|
|
await page.waitForTimeout(100)
|
|
|
|
await u.closeDebugPanel()
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
await page.waitForTimeout(100)
|
|
|
|
const num2 = 23.83
|
|
await expect(
|
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
|
).toBe(
|
|
`${finalCodeFirstSketch}
|
|
const part002 = startSketchOn('XY')
|
|
|> startProfileAt(${startAt2}, %)
|
|
|> line([${num2}, 0], %)`.replace(/\s/g, '')
|
|
)
|
|
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
|
await expect(
|
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
|
).toBe(
|
|
`${finalCodeFirstSketch}
|
|
const part002 = startSketchOn('XY')
|
|
|> startProfileAt(${startAt2}, %)
|
|
|> line([${num2}, 0], %)
|
|
|> line([0, ${num2}], %)`.replace(/\s/g, '')
|
|
)
|
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
|
await expect(
|
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
|
).toBe(
|
|
`${finalCodeFirstSketch}
|
|
const part002 = startSketchOn('XY')
|
|
|> startProfileAt(${startAt2}, %)
|
|
|> line([${num2}, 0], %)
|
|
|> line([0, ${num2}], %)
|
|
|> line([-47.44, 0], %)`.replace(/\s/g, '')
|
|
)
|
|
})
|