Compare commits

..

1 Commits

Author SHA1 Message Date
7f14c7d56d void 2024-04-09 16:01:44 -05:00
412 changed files with 3986 additions and 9835 deletions

View File

@ -9,27 +9,15 @@ updates:
directory: '/' # Location of package manifests directory: '/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'daily'
reviewers:
- franknoirot
- irev-dev
- package-ecosystem: 'github-actions' # See documentation for possible values - package-ecosystem: 'github-actions' # See documentation for possible values
directory: '/' # Location of package manifests directory: '/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'daily'
reviewers:
- adamchalmers
- jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values - package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src/wasm-lib/' # Location of package manifests directory: '/src/wasm-lib/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'daily'
reviewers:
- adamchalmers
- jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values - package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src-tauri/' # Location of package manifests directory: '/src-tauri/' # Location of package manifests
schedule: schedule:
interval: 'daily' interval: 'daily'
reviewers:
- adamchalmers
- jessfraz

View File

@ -104,11 +104,7 @@ jobs:
run: | run: |
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
echo "$(jq --arg url 'https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json' \ echo "$(jq --arg url 'https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json' \
'.plugins.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json '.tauri.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
echo "$(jq --arg id 'dev.zoo.modeling-app-nightly' \
'.identifier=$id' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
echo "$(jq --arg name 'Zoo Modeling App (Nightly)' \
'.productName=$name' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
if: github.event_name == 'schedule' if: github.event_name == 'schedule'
@ -285,7 +281,6 @@ jobs:
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }} NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }}
BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }} BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }}
WEBSITE_DIR: ${{ github.event_name == 'release' && 'dl.zoo.dev/releases/modeling-app' || 'dl.zoo.dev/releases/modeling-app/nightly' }} WEBSITE_DIR: ${{ github.event_name == 'release' && 'dl.zoo.dev/releases/modeling-app' || 'dl.zoo.dev/releases/modeling-app/nightly' }}
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
steps: steps:
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
@ -300,9 +295,9 @@ jobs:
--arg pub_date "${PUB_DATE}" \ --arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \ --arg notes "${NOTES}" \
--arg darwin_sig "$DARWIN_SIG" \ --arg darwin_sig "$DARWIN_SIG" \
--arg darwin_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}.app.tar.gz" \ --arg darwin_url "$RELEASE_DIR/macos/Zoo%20Modeling%20App.app.tar.gz" \
--arg windows_sig "$WINDOWS_SIG" \ --arg windows_sig "$WINDOWS_SIG" \
--arg windows_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi.zip" \ --arg windows_url "$RELEASE_DIR/msi/Zoo%20Modeling%20App_${VERSION_NO_V}_x64_en-US.msi.zip" \
'{ '{
"version": $version, "version": $version,
"pub_date": $pub_date, "pub_date": $pub_date,
@ -331,8 +326,8 @@ jobs:
--arg version "${VERSION}" \ --arg version "${VERSION}" \
--arg pub_date "${PUB_DATE}" \ --arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \ --arg notes "${NOTES}" \
--arg darwin_url "$RELEASE_DIR/dmg/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_universal.dmg" \ --arg darwin_url "$RELEASE_DIR/dmg/Zoo%20Modeling%20App_${VERSION_NO_V}_universal.dmg" \
--arg windows_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi" \ --arg windows_url "$RELEASE_DIR/msi/Zoo%20Modeling%20App_${VERSION_NO_V}_x64_en-US.msi" \
'{ '{
"version": $version, "version": $version,
"pub_date": $pub_date, "pub_date": $pub_date,

View File

@ -56,9 +56,6 @@ jobs:
gh pr create --title "Update KCL docs" \ gh pr create --title "Update KCL docs" \
--body "Updating the generated kcl docs cc @jessfraz @franknoirot merge this" \ --body "Updating the generated kcl docs cc @jessfraz @franknoirot merge this" \
--head "$NEW_BRANCH" \ --head "$NEW_BRANCH" \
--reviewer jessfraz \
--reviewer irev-dev \
--reviewer franknoirot \
--base main || true --base main || true
env: env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 120 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -26564,7 +26564,7 @@
"unpublished": false, "unpublished": false,
"deprecated": false, "deprecated": false,
"examples": [ "examples": [
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, 'revolveAxis')\n |> close(%)\n |> extrude(10, %)\n\nconst sketch001 = startSketchOn('XY')\n |> startProfileAt([0, -10], %)\n |> line([0, -10], %)\n |> line([2, 0], %)\n |> line([0, 10], %)\n |> close(%)\n |> revolve({\n axis: getEdge('revolveAxis', box),\n angle: 90\n }, %)" "const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, 'revolveAxis')\n |> close(%)\n |> extrude(10, %)\n\nconst sketch001 = startSketchOn(box, \"revolveAxis\")\n |> startProfileAt([5, 10], %)\n |> line([0, -10], %)\n |> line([2, 0], %)\n |> line([0, 10], %)\n |> close(%)\n |> revolve({\n axis: getEdge('revolveAxis', box),\n angle: 90\n }, %)"
] ]
}, },
{ {
@ -49363,21 +49363,21 @@
"description": "X-axis.", "description": "X-axis.",
"type": "string", "type": "string",
"enum": [ "enum": [
"X" "x"
] ]
}, },
{ {
"description": "Y-axis.", "description": "Y-axis.",
"type": "string", "type": "string",
"enum": [ "enum": [
"Y" "y"
] ]
}, },
{ {
"description": "Z-axis.", "description": "Z-axis.",
"type": "string", "type": "string",
"enum": [ "enum": [
"Z" "z"
] ]
}, },
{ {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,15 +2,10 @@ import { test, expect } from '@playwright/test'
import { getUtils } from './test-utils' import { getUtils } from './test-utils'
import waitOn from 'wait-on' import waitOn from 'wait-on'
import { roundOff } from 'lib/utils' import { roundOff } from 'lib/utils'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes' import { basicStorageState } from './storageStates'
import { secrets } from './secrets'
import {
TEST_SETTINGS,
TEST_SETTINGS_KEY,
TEST_SETTINGS_CORRUPTED,
TEST_SETTINGS_ONBOARDING,
} from './storageStates'
import * as TOML from '@iarna/toml' import * as TOML from '@iarna/toml'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { Themes } from 'lib/theme'
/* /*
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
@ -37,25 +32,13 @@ test.beforeEach(async ({ context, page }) => {
timeout: 5000, timeout: 5000,
}) })
await context.addInitScript(
async ({ token, settingsKey, settings }) => {
localStorage.setItem('TOKEN_PERSIST_KEY', token)
localStorage.setItem('persistCode', ``)
localStorage.setItem(settingsKey, settings)
},
{
token: secrets.token,
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS }),
}
)
// kill animations, speeds up tests and reduced flakiness // kill animations, speeds up tests and reduced flakiness
await page.emulateMedia({ reducedMotion: 'reduce' }) await page.emulateMedia({ reducedMotion: 'reduce' })
}) })
test.setTimeout(60000) test.setTimeout(60000)
test('Basic sketch', async ({ page }) => { test('Basic sketch', async ({ page, context }) => {
const u = getUtils(page) const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -145,7 +128,6 @@ test('Can moving camera', async ({ page, context }) => {
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await u.closeKclCodePanel()
const camPos: [number, number, number] = [0, 85, 85] const camPos: [number, number, number] = [0, 85, 85]
const bakeInRetries = async ( const bakeInRetries = async (
@ -179,8 +161,6 @@ test('Can moving camera', async ({ page, context }) => {
}, 300) }, 300)
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await page.getByTestId('cam-x-position').isVisible()
const vals = await Promise.all([ const vals = await Promise.all([
page.getByTestId('cam-x-position').inputValue(), page.getByTestId('cam-x-position').inputValue(),
page.getByTestId('cam-y-position').inputValue(), page.getByTestId('cam-y-position').inputValue(),
@ -328,61 +308,9 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
}) })
/* Ignore this test for now since its causing engine to crash test('executes on load', async ({ page, context }) => {
*
* test('if your kcl gets an error from the engine it is inlined', async ({
page,
}) => {
const u = getUtils(page) const u = getUtils(page)
await page.addInitScript(async () => { await context.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: getEdge('revolveAxis', box),
angle: 90
}, %)
`
)
})
await page.setViewportSize({ width: 1000, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// 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(
'sketch profile must lie entirely on one side of the revolution axis'
)
).toBeVisible()
})*/
test('executes on load', async ({ page }) => {
const u = getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('-XZ') `const part001 = startSketchOn('-XZ')
@ -397,11 +325,7 @@ test('executes on load', async ({ page }) => {
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
// expand variables section // expand variables section
const variablesTabButton = page.getByRole('tab', { await page.getByText('Variables').click()
name: 'Variables',
exact: false,
})
await variablesTabButton.click()
// can find part001 in the variables summary (pretty-json-container, makes sure we're not looking in the code editor) // 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 // part001 only shows up in the variables summary if it's been executed
@ -416,20 +340,16 @@ test('executes on load', async ({ page }) => {
).toBeVisible() ).toBeVisible()
}) })
test('re-executes', async ({ page }) => { test('re-executes', async ({ page, context }) => {
const u = getUtils(page) const u = getUtils(page)
await page.addInitScript(async () => { await context.addInitScript(async (token) => {
localStorage.setItem('persistCode', `const myVar = 5`) localStorage.setItem('persistCode', `const myVar = 5`)
}) })
await page.setViewportSize({ width: 1000, height: 500 }) await page.setViewportSize({ width: 1000, height: 500 })
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
const variablesTabButton = page.getByRole('tab', { await page.getByText('Variables').click()
name: 'Variables',
exact: false,
})
await variablesTabButton.click()
// expect to see "myVar:5" // expect to see "myVar:5"
await expect( await expect(
page.locator('.pretty-json-container >> text=myVar:5') page.locator('.pretty-json-container >> text=myVar:5')
@ -446,11 +366,9 @@ test('re-executes', async ({ page }) => {
).toBeVisible() ).toBeVisible()
}) })
const sketchOnPlaneAndBackSideTest = async ( test('Can create sketches on all planes and their back sides', async ({
page: any, page,
plane: string, }) => {
clickCoords: { x: number; y: number }
) => {
const u = getUtils(page) const u = getUtils(page)
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
@ -458,20 +376,22 @@ const sketchOnPlaneAndBackSideTest = async (
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await u.openDebugPanel()
const camCmdBackSide: [number, number, number] = [-100, -100, -100] const camPos: [number, number, number] = [100, 100, 100]
let camPos: [number, number, number] = [100, 100, 100]
if (plane === '-XY' || plane === '-YZ' || plane === '-XZ') {
camPos = camCmdBackSide
}
const code = `const part001 = startSketchOn('${plane}')
|> startProfileAt([1.14, -1.54], %)`
const TestSinglePlane = async ({
viewCmd,
expectedCode,
clickCoords,
}: {
viewCmd: [number, number, number]
expectedCode: string
clickCoords: { x: number; y: number }
}) => {
await u.openDebugPanel() await u.openDebugPanel()
await u.clearCommandLogs() await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click() await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.updateCamPosition(camPos) await u.updateCamPosition(viewCmd)
await u.closeDebugPanel() await u.closeDebugPanel()
await page.mouse.click(clickCoords.x, clickCoords.y) await page.mouse.click(clickCoords.x, clickCoords.y)
@ -485,7 +405,7 @@ const sketchOnPlaneAndBackSideTest = async (
await u.closeDebugPanel() await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')).toHaveText(code) await expect(page.locator('.cm-content')).toHaveText(expectedCode)
await page.getByRole('button', { name: 'Line' }).click() await page.getByRole('button', { name: 'Line' }).click()
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
@ -496,34 +416,41 @@ const sketchOnPlaneAndBackSideTest = async (
await u.removeCurrentCode() await u.removeCurrentCode()
} }
test.describe('Can create sketches on all planes and their back sides', () => { const codeTemplate = (
test('XY', async ({ page }) => { plane = 'XY'
await sketchOnPlaneAndBackSideTest( ) => `const part001 = startSketchOn('${plane}')
page, |> startProfileAt([1.14, -1.54], %)`
'XY', await TestSinglePlane({
{ x: 600, y: 388 } // red plane viewCmd: camPos,
// { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too. 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({
test('YZ', async ({ page }) => { viewCmd: camPos,
await sketchOnPlaneAndBackSideTest(page, 'YZ', { x: 700, y: 250 }) // green plane expectedCode: codeTemplate('YZ'),
clickCoords: { x: 700, y: 250 }, // green plane
}) })
await TestSinglePlane({
test('XZ', async ({ page }) => { viewCmd: camPos,
await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 80 }) // blue plane expectedCode: codeTemplate('XZ'),
clickCoords: { x: 700, y: 80 }, // blue plane
}) })
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
test('-XY', async ({ page }) => { await TestSinglePlane({
await sketchOnPlaneAndBackSideTest(page, '-XY', { x: 600, y: 118 }) // back of red plane viewCmd: camCmdBackSide,
expectedCode: codeTemplate('-XY'),
clickCoords: { x: 601, y: 118 }, // back of red plane
}) })
await TestSinglePlane({
test('-YZ', async ({ page }) => { viewCmd: camCmdBackSide,
await sketchOnPlaneAndBackSideTest(page, '-YZ', { x: 700, y: 219 }) // back of green plane expectedCode: codeTemplate('-YZ'),
clickCoords: { x: 730, y: 219 }, // back of green plane
}) })
await TestSinglePlane({
test('-XZ', async ({ page }) => { viewCmd: camCmdBackSide,
await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 427 }) // back of blue plane expectedCode: codeTemplate('-XZ'),
clickCoords: { x: 680, y: 427 }, // back of blue plane
}) })
}) })
@ -555,8 +482,7 @@ test('Auto complete works', async ({ page }) => {
// expect there to be three auto complete options // expect there to be three auto complete options
await expect(page.locator('.cm-completionLabel')).toHaveCount(3) await expect(page.locator('.cm-completionLabel')).toHaveCount(3)
await page.getByText('startSketchOn').click() await page.getByText('startSketchOn').click()
await page.keyboard.type("'XZ'") await page.keyboard.type("('XY')")
await page.keyboard.press('Tab')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
await page.keyboard.type(' |> startProfi') await page.keyboard.type(' |> startProfi')
// expect there be a single auto complete option that we can just hit enter on // expect there be a single auto complete option that we can just hit enter on
@ -564,11 +490,7 @@ test('Auto complete works', async ({ page }) => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.keyboard.press('Enter') // accepting the auto complete, not a new line await page.keyboard.press('Enter') // accepting the auto complete, not a new line
await page.keyboard.press('Tab') await page.keyboard.type('([0,0], %)')
await page.keyboard.type('12')
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
await page.keyboard.type(' |> lin') await page.keyboard.type(' |> lin')
@ -579,48 +501,45 @@ test('Auto complete works', async ({ page }) => {
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// finish line with comment // finish line with comment
await page.keyboard.type('5') await page.keyboard.type('(5, %) // lin')
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')
await page.keyboard.type(' // lin')
await page.waitForTimeout(100) await page.waitForTimeout(100)
// there shouldn't be any auto complete options for 'lin' in the comment // 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-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ') .toHaveText(`const part001 = startSketchOn('XY')
|> startProfileAt([3.14, 12], %) |> startProfileAt([0,0], %)
|> xLine(5, %) // lin`) |> xLine(5, %) // lin`)
}) })
// Stored settings validation test
test.describe('Settings persistence and validation tests', () => {
// Override test setup
// with corrupted settings
const storageState = structuredClone(basicStorageState)
const s = TOML.parse(storageState.origins[0].localStorage[2].value) as {
settings: SaveSettingsPayload
}
s.settings.app.theme = Themes.Dark
s.settings.app.projectDirectory = 123 as any
s.settings.modeling.defaultUnit = 'invalid' as any
s.settings.modeling.mouseControls = `() => alert('hack the planet')` as any
s.settings.projects.defaultProjectName = false as any
storageState.origins[0].localStorage[2].value = TOML.stringify(s)
test.use({ storageState })
test('Stored settings are validated and fall back to defaults', async ({ test('Stored settings are validated and fall back to defaults', async ({
page, page,
context,
}) => { }) => {
const u = getUtils(page) const u = getUtils(page)
// Override beforeEach test setup
// with corrupted settings
await context.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 page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
// Check the settings were reset // Check the settings were reset
const storedSettings = TOML.parse( const storedSettings = TOML.parse(
await page.evaluate( await page.evaluate(() => localStorage.getItem('/user.toml') || '{}')
({ settingsKey }) => localStorage.getItem(settingsKey) || '{}',
{ settingsKey: TEST_SETTINGS_KEY }
)
) as { settings: SaveSettingsPayload } ) as { settings: SaveSettingsPayload }
expect(storedSettings.settings.app?.theme).toBe('dark') expect(storedSettings.settings.app?.theme).toBe('dark')
@ -635,11 +554,10 @@ test('Stored settings are validated and fall back to defaults', async ({
test('Project settings can be set and override user settings', async ({ test('Project settings can be set and override user settings', async ({
page, page,
}) => { }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/', { waitUntil: 'domcontentloaded' }) await page.goto('/')
await page await u.waitForAuthSkipAppStart()
.getByRole('button', { name: 'Start Sketch' })
.waitFor({ state: 'visible' })
// Open the settings modal with the browser keyboard shortcut // Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('Meta+Shift+,') await page.keyboard.press('Meta+Shift+,')
@ -679,22 +597,21 @@ test('Project settings can be set and override user settings', async ({
await page.getByRole('radio', { name: 'Project' }).click() await page.getByRole('radio', { name: 'Project' }).click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light') await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
}) })
})
test('Onboarding redirects and code updating', async ({ page }) => { // Onboarding tests
const u = getUtils(page) test.describe('Onboarding tests', () => {
// Override test setup
// Override beforeEach test setup const storageState = structuredClone(basicStorageState)
await page.addInitScript( const s = TOML.parse(storageState.origins[0].localStorage[2].value) as {
async ({ settingsKey, settings }) => { settings: SaveSettingsPayload
// 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 }),
} }
) s.settings.app.onboardingStatus = '/export'
storageState.origins[0].localStorage[2].value = TOML.stringify(s)
test.use({ storageState })
test('Onboarding redirects and code updating', async ({ page, context }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/') await page.goto('/')
@ -723,6 +640,7 @@ test('Onboarding redirects and code updating', async ({ page }) => {
await page.locator('[data-testid="onboarding-next"]').click() await page.locator('[data-testid="onboarding-next"]').click()
await expect(page.locator('.cm-content')).toHaveText(/.+/) await expect(page.locator('.cm-content')).toHaveText(/.+/)
}) })
})
test('Selections work on fresh and edited sketch', async ({ page }) => { test('Selections work on fresh and edited sketch', async ({ page }) => {
// tests mapping works on fresh sketch and edited sketch // tests mapping works on fresh sketch and edited sketch
@ -933,11 +851,9 @@ test.describe('Command bar tests', () => {
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`) await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
}) })
test('Can extrude from the command bar', async ({ page }) => { // Override test setup code
await page.addInitScript(async () => { const storageState = structuredClone(basicStorageState)
localStorage.setItem( storageState.origins[0].localStorage[1].value = `const distance = sqrt(20)
'persistCode',
`const distance = sqrt(20)
const part001 = startSketchOn('-XZ') const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 4.98], %) |> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %) |> line([25.1, 0.41], %)
@ -945,9 +861,9 @@ test.describe('Command bar tests', () => {
|> line([-23.44, 0.52], %) |> line([-23.44, 0.52], %)
|> close(%) |> close(%)
` `
) test.use({ storageState })
})
test('Can extrude from the command bar', async ({ page, context }) => {
const u = getUtils(page) const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/') await page.goto('/')
@ -956,13 +872,15 @@ test.describe('Command bar tests', () => {
// Make sure the stream is up // Make sure the stream is up
await u.openDebugPanel() await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled()
await u.clearCommandLogs() await page.getByText('|> startProfileAt([-6.95, 4.98], %)').click()
await page.getByText('|> line([0.73, -14.93], %)').click() await expect(
await page.getByRole('button', { name: 'Extrude' }).isEnabled() page.getByRole('button', { name: 'Extrude' })
).not.toBeDisabled()
let cmdSearchBar = page.getByPlaceholder('Search commands') let cmdSearchBar = page.getByPlaceholder('Search commands')
await page.keyboard.press('Meta+K') await page.keyboard.press('Meta+K')
@ -980,25 +898,23 @@ test.describe('Command bar tests', () => {
await expect(page.getByPlaceholder('Variable name')).toHaveValue( await expect(page.getByPlaceholder('Variable name')).toHaveValue(
'distance001' 'distance001'
) )
await expect(page.getByRole('button', { name: 'Continue' })).toBeEnabled()
const continueButton = page.getByRole('button', { name: 'Continue' }) await page.getByRole('button', { name: 'Continue' }).click()
const submitButton = page.getByRole('button', { name: 'Submit command' })
await continueButton.click()
// Review step and argument hotkeys // Review step and argument hotkeys
await expect(submitButton).toBeEnabled() await expect(
page.getByRole('button', { name: 'Submit command' })
).toBeEnabled()
await page.keyboard.press('Backspace') await page.keyboard.press('Backspace')
// Assert we're back on the distance step
await expect( await expect(
page.getByRole('button', { name: 'Distance 12', exact: false }) page.getByRole('button', { name: 'Distance 12', exact: false })
).toBeDisabled() ).toBeDisabled()
await page.keyboard.press('Enter')
await continueButton.click() await expect(page.getByText('Confirm Extrude')).toBeVisible()
await submitButton.click()
// Check that the code was updated // Check that the code was updated
await u.waitForCmdReceive('extrude') await page.keyboard.press('Enter')
// Unfortunately this indentation seems to matter for the test // Unfortunately this indentation seems to matter for the test
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`const distance = sqrt(20) `const distance = sqrt(20)
@ -1139,9 +1055,9 @@ const part002 = startSketchOn('XY')
) )
}) })
test('ProgramMemory can be serialised', async ({ page }) => { test('ProgramMemory can be serialised', async ({ page, context }) => {
const u = getUtils(page) const u = getUtils(page)
await page.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part = startSketchOn('XY') `const part = startSketchOn('XY')
@ -1151,7 +1067,7 @@ test('ProgramMemory can be serialised', async ({ page }) => {
|> line([0, -1], %) |> line([0, -1], %)
|> close(%) |> close(%)
|> extrude(1, %) |> extrude(1, %)
|> patternLinear3d({ |> patternLinear({
axis: [1, 0, 1], axis: [1, 0, 1],
repetitions: 3, repetitions: 3,
distance: 6 distance: 6
@ -1180,6 +1096,7 @@ test('ProgramMemory can be serialised', async ({ page }) => {
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({ test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
page, page,
context,
}) => { }) => {
const u = getUtils(page) const u = getUtils(page)
const selectionsSnippets = { const selectionsSnippets = {
@ -1188,7 +1105,7 @@ test("Various pipe expressions should and shouldn't allow edit and or extrude",
extrudeAndEditAllowed: '|> startProfileAt([15.72, 4.7], %)', extrudeAndEditAllowed: '|> startProfileAt([15.72, 4.7], %)',
editOnly: '|> startProfileAt([15.79, -14.6], %)', editOnly: '|> startProfileAt([15.79, -14.6], %)',
} }
await page.addInitScript( await context.addInitScript(
async ({ async ({
extrudeAndEditBlocked, extrudeAndEditBlocked,
extrudeAndEditBlockedInFunction, extrudeAndEditBlockedInFunction,
@ -1235,7 +1152,7 @@ fn yohey = (pos) => {
}, },
selectionsSnippets selectionsSnippets
) )
await page.setViewportSize({ width: 1200, height: 1000 }) await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
@ -1348,9 +1265,12 @@ test('Deselecting line tool should mean nothing happens on click', async ({
previousCodeContent = await page.locator('.cm-content').innerText() previousCodeContent = await page.locator('.cm-content').innerText()
}) })
test('Can edit segments by dragging their handles', async ({ page }) => { test('Can edit segments by dragging their handles', async ({
page,
context,
}) => {
const u = getUtils(page) const u = getUtils(page)
await page.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('-XZ') `const part001 = startSketchOn('-XZ')
@ -1419,29 +1339,22 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
|> tangentialArcTo([27.6, -3.25], %)`) |> tangentialArcTo([27.6, -3.25], %)`)
}) })
const doSnapAtDifferentScales = async ( test('Snap to close works (at any scale)', async ({ page }) => {
page: any,
camPos: [number, number, number],
scale = 1,
fudge = 0
) => {
const u = getUtils(page) const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await u.openDebugPanel()
const code = `const part001 = startSketchOn('XZ')
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|> line([${roundOff(scale * 175.36)}, 0], %)
|> line([0, -${roundOff(scale * 175.36) + fudge}], %)
|> close(%)`
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible() await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
const doSnapAtDifferentScales = async (
camPos: [number, number, number],
expectedCode: string
) => {
await u.clearCommandLogs() await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click() await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -1486,7 +1399,7 @@ const doSnapAtDifferentScales = async (
await expect(page.locator('.cm-content')).not.toHaveText(prevContent) await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText() prevContent = await page.locator('.cm-content').innerText()
await expect(page.locator('.cm-content')).toHaveText(code) await expect(page.locator('.cm-content')).toHaveText(expectedCode)
// exit sketch // exit sketch
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
@ -1495,19 +1408,23 @@ const doSnapAtDifferentScales = async (
await u.removeCurrentCode() await u.removeCurrentCode()
} }
test.describe('Snap to close works (at any scale)', () => { const codeTemplate = (
test('[0, 100, 100]', async ({ page }) => { scale = 1,
await doSnapAtDifferentScales(page, [0, 100, 100], 0.01, 0.01) fudge = 0
) => `const part001 = startSketchOn('XZ')
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|> line([${roundOff(scale * 175.36)}, 0], %)
|> line([0, -${roundOff(scale * 175.36) + fudge}], %)
|> close(%)`
await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01))
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
}) })
test('[0, 10000, 10000]', async ({ page }) => { test('Sketch on face', async ({ page, context }) => {
await doSnapAtDifferentScales(page, [0, 10000, 10000])
})
})
test('Sketch on face', async ({ page }) => {
const u = getUtils(page) const u = getUtils(page)
await page.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const part001 = startSketchOn('-XZ') `const part001 = startSketchOn('-XZ')
@ -1632,44 +1549,3 @@ test('Sketch on face', async ({ page }) => {
|> close(%) |> close(%)
|> extrude(5 + 7, %)`) |> extrude(5 + 7, %)`)
}) })
test('Can code mod a line length', async ({ page }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> xLine(-20, %)
`
)
})
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()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// Click the line of code for xLine.
await page.getByText(`xLine(-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(300) // wait for animation
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.mouse.click(615, 133)
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 part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> line([0, 20], %) |> xLine(-length001, %)`
)
})

View File

@ -7,36 +7,16 @@ import { spawn } from 'child_process'
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
import JSZip from 'jszip' import JSZip from 'jszip'
import path from 'path' import path from 'path'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates' import { basicSettings, basicStorageState } from './storageStates'
import * as TOML from '@iarna/toml' import * as TOML from '@iarna/toml'
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// reducedMotion kills animations, which speeds up tests and reduces flakiness // reducedMotion kills animations, which speeds up tests and reduces flakiness
await page.emulateMedia({ reducedMotion: 'reduce' }) await page.emulateMedia({ reducedMotion: 'reduce' })
// set the default settings
await page.addInitScript(
async ({ token, settingsKey, settings }) => {
localStorage.setItem('TOKEN_PERSIST_KEY', token)
localStorage.setItem('persistCode', ``)
localStorage.setItem(settingsKey, settings)
},
{
token: secrets.token,
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS }),
}
)
// Make the user avatar image always 404
// so we see the fallback menu icon for all snapshot tests
await page.route('https://lh3.googleusercontent.com/**', async (route) => {
await route.fulfill({
status: 404,
contentType: 'text/plain',
body: 'Not Found!',
})
}) })
test.use({
storageState: structuredClone(basicStorageState),
}) })
test.setTimeout(60_000) test.setTimeout(60_000)
@ -336,7 +316,10 @@ const part001 = startSketchOn('-XZ')
} }
}) })
const extrudeDefaultPlane = async (context: any, page: any, plane: string) => { test('extrude on each default plane should be stable', async ({
page,
context,
}) => {
await context.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'SETTINGS_PERSIST_KEY', 'SETTINGS_PERSIST_KEY',
@ -353,8 +336,8 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
}) })
) )
}) })
const u = getUtils(page)
const code = `const part001 = startSketchOn('${plane}') const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|> startProfileAt([7.00, 4.40], %) |> startProfileAt([7.00, 4.40], %)
|> line([6.60, -0.20], %) |> line([6.60, -0.20], %)
|> line([2.80, 5.00], %) |> line([2.80, 5.00], %)
@ -363,11 +346,9 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|> close(%) |> close(%)
|> extrude(10.00, %) |> extrude(10.00, %)
` `
await page.addInitScript(async (code: string) => { await context.addInitScript(async (code) => {
localStorage.setItem('persistCode', code) localStorage.setItem('persistCode', code)
}) }, makeCode('XY'))
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
@ -377,48 +358,32 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel() await u.clearAndCloseDebugPanel()
await page.waitForTimeout(200) await page.waitForTimeout(200)
const runSnapshotsForOtherPlanes = async (plane = 'XY') => {
// clear code // clear code
await u.removeCurrentCode() await u.removeCurrentCode()
// add makeCode('XZ')
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await u.doAndWaitForImageDiff( await page.locator('.cm-content').fill(makeCode(plane))
() => page.locator('.cm-content').fill(code),
200
)
// wait for execution done // wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel() await u.clearAndCloseDebugPanel()
await u.closeKclCodePanel() await page.getByText('Code').click()
await page.waitForTimeout(150)
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
await u.openKclCodePanel() await page.getByText('Code').click()
} }
await runSnapshotsForOtherPlanes('XY')
await runSnapshotsForOtherPlanes('-XY')
test.describe('extrude on default planes should be stable', () => { await runSnapshotsForOtherPlanes('XZ')
test('XY', async ({ page, context }) => { await runSnapshotsForOtherPlanes('-XZ')
await extrudeDefaultPlane(context, page, 'XY')
})
test('XZ', async ({ page, context }) => { await runSnapshotsForOtherPlanes('YZ')
await extrudeDefaultPlane(context, page, 'XZ') await runSnapshotsForOtherPlanes('-YZ')
})
test('YZ', async ({ page, context }) => {
await extrudeDefaultPlane(context, page, 'YZ')
})
test('-XY', async ({ page, context }) => {
await extrudeDefaultPlane(context, page, '-XY')
})
test('-XZ', async ({ page, context }) => {
await extrudeDefaultPlane(context, page, '-XZ')
})
test('-YZ', async ({ page, context }) => {
await extrudeDefaultPlane(context, page, '-YZ')
})
}) })
test('Draft segments should look right', async ({ page, context }) => { test('Draft segments should look right', async ({ page, context }) => {
@ -480,7 +445,9 @@ test('Draft segments should look right', async ({ page, context }) => {
}) })
}) })
test('Draft rectangles should look right', async ({ page, context }) => { test('Client side scene scale should match engine scale - Inch', async ({
page,
}) => {
const u = getUtils(page) const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -507,55 +474,6 @@ test('Draft rectangles should look right', async ({ page, context }) => {
`const part001 = startSketchOn('-XZ')` `const part001 = startSketchOn('-XZ')`
) )
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
await u.closeDebugPanel()
const startXPx = 600
// Equip the rectangle tool
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Rectangle' }).click()
// Draw the rectangle
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 30)
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 })
// Ensure the draft rectangle looks the same as it usually does
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
})
test.describe('Client side scene scale should match engine scale', () => {
test('Inch scale', 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' })
).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
)
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
const startXPx = 600 const startXPx = 600
@ -610,24 +528,22 @@ test.describe('Client side scene scale should match engine scale', () => {
}) })
}) })
test('Millimeter scale', async ({ page }) => { test.describe('Client side scene scale should match engine scale - Millimeters', () => {
await page.addInitScript( const storageState = structuredClone(basicStorageState)
async ({ settingsKey, settings }) => { storageState.origins[0].localStorage[2].value = TOML.stringify({
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({
settings: { settings: {
...TEST_SETTINGS, ...basicSettings,
modeling: { modeling: {
...TEST_SETTINGS.modeling, ...basicSettings.modeling,
defaultUnit: 'mm', defaultUnit: 'mm',
}, },
}, },
}), })
} test.use({
) storageState,
})
test('Millimeters', async ({ page }) => {
const u = getUtils(page) const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -722,7 +638,7 @@ test('Sketch on face with none z-up', async ({ page, context }) => {
|> close(%) |> close(%)
|> extrude(5 + 7, %) |> extrude(5 + 7, %)
const part002 = startSketchOn(part001, 'seg01') const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([8, 8], %) |> startProfileAt([-2.89, 1.82], %)
|> line([4.68, 3.05], %) |> line([4.68, 3.05], %)
|> line([0, -7.79], %, 'seg02') |> line([0, -7.79], %, 'seg02')
|> close(%) |> close(%)
@ -734,19 +650,6 @@ const part002 = startSketchOn(part001, 'seg01')
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
// wait for execution done
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(2)
await u.closeDebugPanel()
// Wait for the second extrusion to appear
// TODO: Find a way to truly know that the objects have finished
// rendering, because an execution-done message is not sufficient.
await page.waitForTimeout(1000)
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -1,8 +1,9 @@
import { SaveSettingsPayload } from 'lib/settings/settingsTypes' import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { secrets } from './secrets'
import * as TOML from '@iarna/toml'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
export const TEST_SETTINGS_KEY = '/user.toml' export const basicSettings = {
export const TEST_SETTINGS = {
app: { app: {
theme: Themes.Dark, theme: Themes.Dark,
onboardingStatus: 'dismissed', onboardingStatus: 'dismissed',
@ -21,26 +22,19 @@ export const TEST_SETTINGS = {
}, },
} satisfies Partial<SaveSettingsPayload> } satisfies Partial<SaveSettingsPayload>
export const TEST_SETTINGS_ONBOARDING = { export const basicStorageState = {
...TEST_SETTINGS, cookies: [],
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export ' }, origins: [
} satisfies Partial<SaveSettingsPayload> {
origin: 'http://localhost:3000',
export const TEST_SETTINGS_CORRUPTED = { localStorage: [
app: { { name: 'TOKEN_PERSIST_KEY', value: secrets.token },
theme: Themes.Dark, { name: 'persistCode', value: '' },
onboardingStatus: 'dismissed', {
projectDirectory: 123 as any, name: '/user.toml',
value: TOML.stringify({ settings: basicSettings }),
}, },
modeling: { ],
defaultUnit: 'invalid' as any,
mouseControls: `() => alert('hack the planet')` as any,
showDebugPanel: true,
}, },
projects: { ],
defaultProjectName: false as any, }
},
textEditor: {
textWrapping: true,
},
} satisfies Partial<SaveSettingsPayload>

View File

@ -44,44 +44,26 @@ async function waitForDefaultPlanesToBeVisible(page: Page) {
) )
} }
async function openKclCodePanel(page: Page) {
const paneLocator = page.getByRole('tab', { name: 'KCL Code', exact: false })
const isOpen = (await paneLocator?.getAttribute('aria-selected')) === 'true'
if (!isOpen) {
await paneLocator.click()
await paneLocator.and(page.locator('[aria-selected="true"]')).waitFor()
}
}
async function closeKclCodePanel(page: Page) {
const paneLocator = page.getByRole('tab', { name: 'KCL Code', exact: false })
const isOpen = (await paneLocator?.getAttribute('aria-selected')) === 'true'
if (isOpen) {
await paneLocator.click()
await paneLocator
.and(page.locator(':not([aria-selected="true"])'))
.waitFor()
}
}
async function openDebugPanel(page: Page) { async function openDebugPanel(page: Page) {
const debugLocator = page.getByRole('tab', { name: 'Debug', exact: false }) const isOpen =
const isOpen = (await debugLocator?.getAttribute('aria-selected')) === 'true' (await page
.locator('[data-testid="debug-panel"]')
?.getAttribute('open')) === ''
if (!isOpen) { if (!isOpen) {
await debugLocator.click() await page.getByText('Debug').click()
await debugLocator.and(page.locator('[aria-selected="true"]')).waitFor() await page.getByTestId('debug-panel').and(page.locator('[open]')).waitFor()
} }
} }
async function closeDebugPanel(page: Page) { async function closeDebugPanel(page: Page) {
const debugLocator = page.getByRole('tab', { name: 'Debug', exact: false }) const isOpen =
const isOpen = (await debugLocator?.getAttribute('aria-selected')) === 'true' (await page.getByTestId('debug-panel')?.getAttribute('open')) === ''
if (isOpen) { if (isOpen) {
await debugLocator.click() await page.getByText('Debug').click()
await debugLocator await page
.and(page.locator(':not([aria-selected="true"])')) .getByTestId('debug-panel')
.and(page.locator(':not([open])'))
.waitFor() .waitFor()
} }
} }
@ -99,19 +81,20 @@ export function getUtils(page: Page) {
removeCurrentCode: () => removeCurrentCode(page), removeCurrentCode: () => removeCurrentCode(page),
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd), sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
updateCamPosition: async (xyz: [number, number, number]) => { updateCamPosition: async (xyz: [number, number, number]) => {
const fillInput = async (axis: 'x' | 'y' | 'z', value: number) => { const fillInput = async () => {
await page.fill(`[data-testid="cam-${axis}-position"]`, String(value)) await page.fill('[data-testid="cam-x-position"]', String(xyz[0]))
await page.waitForTimeout(100) await page.fill('[data-testid="cam-y-position"]', String(xyz[1]))
await page.fill('[data-testid="cam-z-position"]', String(xyz[2]))
} }
await fillInput()
await fillInput('x', xyz[0]) await page.waitForTimeout(100)
await fillInput('y', xyz[1]) await fillInput()
await fillInput('z', xyz[2]) await page.waitForTimeout(100)
await fillInput()
await page.waitForTimeout(100)
}, },
clearCommandLogs: () => clearCommandLogs(page), clearCommandLogs: () => clearCommandLogs(page),
expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr), expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr),
openKclCodePanel: () => openKclCodePanel(page),
closeKclCodePanel: () => closeKclCodePanel(page),
openDebugPanel: () => openDebugPanel(page), openDebugPanel: () => openDebugPanel(page),
closeDebugPanel: () => closeDebugPanel(page), closeDebugPanel: () => closeDebugPanel(page),
openAndClearDebugPanel: async () => { openAndClearDebugPanel: async () => {

View File

@ -3,34 +3,33 @@
"version": "0.17.3", "version": "0.17.3",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.16.0", "@codemirror/autocomplete": "^6.15.0",
"@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-brands-svg-icons": "^6.5.2", "@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.18", "@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0", "@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.58", "@iarna/toml": "^2.2.5",
"@kittycad/lib": "^0.0.56",
"@lezer/javascript": "^1.4.9", "@lezer/javascript": "^1.4.9",
"@open-rpc/client-js": "^1.8.1", "@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6", "@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.1", "@replit/codemirror-interact": "^6.3.0",
"@tauri-apps/api": "2.0.0-beta.8", "@tauri-apps/api": "^2.0.0-beta.7",
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2", "@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
"@tauri-apps/plugin-fs": "^2.0.0-beta.2", "@tauri-apps/plugin-fs": "^2.0.0-beta.2",
"@tauri-apps/plugin-http": "^2.0.0-beta.2", "@tauri-apps/plugin-http": "^2.0.0-beta.2",
"@tauri-apps/plugin-os": "^2.0.0-beta.2", "@tauri-apps/plugin-os": "^2.0.0-beta.2",
"@tauri-apps/plugin-process": "^2.0.0-beta.2",
"@tauri-apps/plugin-shell": "^2.0.0-beta.2", "@tauri-apps/plugin-shell": "^2.0.0-beta.2",
"@tauri-apps/plugin-updater": "^2.0.0-beta.2",
"@testing-library/jest-dom": "^5.14.1", "@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^15.0.2", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@ts-stack/markdown": "^1.5.0", "@ts-stack/markdown": "^1.5.0",
"@tweenjs/tween.js": "^23.1.1", "@tweenjs/tween.js": "^23.1.1",
"@types/node": "^18.19.31", "@types/node": "^18.19.26",
"@types/react": "^18.2.77", "@types/react": "^18.2.75",
"@types/react-dom": "^18.2.25", "@types/react-dom": "^18.2.22",
"@uiw/react-codemirror": "^4.21.25", "@uiw/react-codemirror": "^4.21.25",
"@xstate/inspect": "^0.8.0", "@xstate/inspect": "^0.8.0",
"@xstate/react": "^3.2.2", "@xstate/react": "^3.2.2",
@ -39,7 +38,6 @@
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"formik": "^2.4.5", "formik": "^2.4.5",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"html2canvas-pro": "^1.4.3",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"json-rpc-2.0": "^1.6.0", "json-rpc-2.0": "^1.6.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
@ -54,13 +52,13 @@
"react-modal-promise": "^1.0.2", "react-modal-promise": "^1.0.2",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"sketch-helpers": "^0.0.4", "sketch-helpers": "^0.0.4",
"swr": "^2.2.5", "swr": "^2.2.2",
"three": "^0.163.0", "three": "^0.160.0",
"toml": "^3.0.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.5", "typescript": "^5.4.3",
"ua-parser-js": "^1.0.37",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vitest": "^1.5.0", "vitest": "^1.4.0",
"vscode-jsonrpc": "^8.1.0", "vscode-jsonrpc": "^8.1.0",
"vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-protocol": "^3.17.5",
"wasm-pack": "^0.12.1", "wasm-pack": "^0.12.1",
@ -117,26 +115,24 @@
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.24.3", "@babel/preset-env": "^7.24.3",
"@iarna/toml": "^2.2.5", "@playwright/test": "^1.39.0",
"@playwright/test": "^1.43.1", "@tauri-apps/cli": "^2.0.0-beta.12",
"@tauri-apps/cli": "^2.0.0-beta.13",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/debounce-promise": "^3.1.9", "@types/debounce-promise": "^3.1.9",
"@types/pixelmatch": "^5.2.6", "@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/react-modal": "^3.16.3", "@types/react-modal": "^3.16.3",
"@types/three": "^0.163.0", "@types/three": "^0.160.0",
"@types/ua-parser-js": "^0.7.39",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/wait-on": "^5.3.4", "@types/wait-on": "^5.3.4",
"@types/wicg-file-system-access": "^2023.10.5", "@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"@wdio/cli": "^8.24.3", "@wdio/cli": "^8.24.3",
"@wdio/globals": "^8.36.0", "@wdio/globals": "^8.24.3",
"@wdio/local-runner": "^8.36.0", "@wdio/local-runner": "^8.35.1",
"@wdio/mocha-framework": "^8.36.0", "@wdio/mocha-framework": "^8.35.0",
"@wdio/spec-reporter": "^8.36.0", "@wdio/spec-reporter": "^8.32.4",
"@xstate/cli": "^0.5.17", "@xstate/cli": "^0.5.17",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^8.57.0", "eslint": "^8.57.0",
@ -151,12 +147,12 @@
"prettier": "^2.8.0", "prettier": "^2.8.0",
"setimmediate": "^1.0.5", "setimmediate": "^1.0.5",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"vite": "^5.2.9", "vite": "^5.2.6",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-plugin-package-version": "^1.1.0", "vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest-webgl-canvas-mock": "^1.1.0", "vitest-webgl-canvas-mock": "^1.1.0",
"wait-on": "^7.2.0", "wait-on": "^7.2.0",
"yarn": "^1.22.22" "yarn": "^1.22.19"
} }
} }

View File

@ -1,4 +1,5 @@
import { defineConfig, devices } from '@playwright/test' import { defineConfig, devices } from '@playwright/test'
import { basicStorageState } from './e2e/playwright/storageStates'
/** /**
* Read environment variables from file. * Read environment variables from file.
@ -28,6 +29,9 @@ export default defineConfig({
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry', trace: 'on-first-retry',
/* Use a common shared localStorage */
storageState: basicStorageState,
}, },
/* Configure projects for major browsers */ /* Configure projects for major browsers */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 36 KiB

478
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,22 +12,21 @@ rust-version = "1.70"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.0.0-beta.12", features = [] } tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
kittycad = "0.2.67" kittycad = "0.2.63"
oauth2 = "4.4.2" oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] } tauri = { version = "2.0.0-beta", features = [ "devtools", "unstable"] }
tauri-plugin-dialog = { version = "2.0.0-beta.5" } tauri-plugin-dialog = { version = "2.0.0-beta.0" }
tauri-plugin-fs = { version = "2.0.0-beta.5" } tauri-plugin-fs = { version = "2.0.0-beta.0" }
tauri-plugin-http = { version = "2.0.0-beta.5" } tauri-plugin-http = { version = "2.0.0-beta.0" }
tauri-plugin-os = { version = "2.0.0-beta.2" } tauri-plugin-os = { version = "2.0.0-beta.0" }
tauri-plugin-process = { version = "2.0.0-beta.2" } tauri-plugin-shell = { version = "2.0.0-beta.0" }
tauri-plugin-shell = { version = "2.0.0-beta.2" } tauri-plugin-updater = { version = "2.0.0-beta.0" }
tauri-plugin-updater = { version = "2.0.0-beta.4" }
tokio = { version = "1.37.0", features = ["time"] } tokio = { version = "1.37.0", features = ["time"] }
toml = "0.8.2" toml = "0.8.2"

View File

@ -77,9 +77,7 @@
"os:allow-arch", "os:allow-arch",
"os:allow-exe-extension", "os:allow-exe-extension",
"os:allow-locale", "os:allow-locale",
"os:allow-hostname", "os:allow-hostname"
"process:allow-restart",
"updater:default"
], ],
"platforms": [ "platforms": [
"linux", "linux",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Some files were not shown because too many files have changed in this diff Show More