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
schedule:
interval: 'daily'
reviewers:
- franknoirot
- irev-dev
- package-ecosystem: 'github-actions' # See documentation for possible values
directory: '/' # Location of package manifests
schedule:
interval: 'daily'
reviewers:
- adamchalmers
- jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src/wasm-lib/' # Location of package manifests
schedule:
interval: 'daily'
reviewers:
- adamchalmers
- jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src-tauri/' # Location of package manifests
schedule:
interval: 'daily'
reviewers:
- adamchalmers
- jessfraz

View File

@ -104,11 +104,7 @@ jobs:
run: |
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
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
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
'.tauri.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
- uses: actions/upload-artifact@v3
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) }}
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' }}
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
steps:
- uses: actions/download-artifact@v3
@ -300,9 +295,9 @@ jobs:
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--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_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,
"pub_date": $pub_date,
@ -331,8 +326,8 @@ jobs:
--arg version "${VERSION}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg darwin_url "$RELEASE_DIR/dmg/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_universal.dmg" \
--arg windows_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi" \
--arg darwin_url "$RELEASE_DIR/dmg/Zoo%20Modeling%20App_${VERSION_NO_V}_universal.dmg" \
--arg windows_url "$RELEASE_DIR/msi/Zoo%20Modeling%20App_${VERSION_NO_V}_x64_en-US.msi" \
'{
"version": $version,
"pub_date": $pub_date,

View File

@ -15,7 +15,7 @@ concurrency:
cancel-in-progress: true
jobs:
generate-website-docs:
name: generate-website-docs
name: generate-website-docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -56,9 +56,6 @@ jobs:
gh pr create --title "Update KCL docs" \
--body "Updating the generated kcl docs cc @jessfraz @franknoirot merge this" \
--head "$NEW_BRANCH" \
--reviewer jessfraz \
--reviewer irev-dev \
--reviewer franknoirot \
--base main || true
env:
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

View File

@ -17,11 +17,11 @@ lineTo(to: [number], sketch_group: SketchGroup, tag?: String) -> SketchGroup
```js
fn rectShape = (pos, w, l) => {
const rr = startSketchOn('YZ')
|> startProfileAt([pos[0] - (w / 2), pos[1] - (l / 2)], %)
|> lineTo([pos[0] + w / 2, pos[1] - (l / 2)], %, "edge1")
|> lineTo([pos[0] + w / 2, pos[1] + l / 2], %, "edge2")
|> lineTo([pos[0] - (w / 2), pos[1] + l / 2], %, "edge3")
|> close(%, "edge4")
|> startProfileAt([pos[0] - (w / 2), pos[1] - (l / 2)], %)
|> lineTo([pos[0] + w / 2, pos[1] - (l / 2)], %, "edge1")
|> lineTo([pos[0] + w / 2, pos[1] + l / 2], %, "edge2")
|> lineTo([pos[0] - (w / 2), pos[1] + l / 2], %, "edge3")
|> close(%, "edge4")
return rr
}

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,
"deprecated": false,
"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 }, %)"
]
},
{
@ -41924,7 +41924,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"fn rectShape = (pos, w, l) => {\n const rr = startSketchOn('YZ')\n |> startProfileAt([pos[0] - (w / 2), pos[1] - (l / 2)], %)\n |> lineTo([pos[0] + w / 2, pos[1] - (l / 2)], %, \"edge1\")\n |> lineTo([pos[0] + w / 2, pos[1] + l / 2], %, \"edge2\")\n |> lineTo([pos[0] - (w / 2), pos[1] + l / 2], %, \"edge3\")\n |> close(%, \"edge4\")\n return rr\n}\n\n// Create the mounting plate extrusion, holes, and fillets\nconst part = rectShape([0, 0], 20, 20)"
"fn rectShape = (pos, w, l) => {\n const rr = startSketchOn('YZ')\n |> startProfileAt([pos[0] - (w / 2), pos[1] - (l / 2)], %)\n |> lineTo([pos[0] + w / 2, pos[1] - (l / 2)], %, \"edge1\")\n |> lineTo([pos[0] + w / 2, pos[1] + l / 2], %, \"edge2\")\n |> lineTo([pos[0] - (w / 2), pos[1] + l / 2], %, \"edge3\")\n |> close(%, \"edge4\")\n return rr\n}\n\n// Create the mounting plate extrusion, holes, and fillets\nconst part = rectShape([0, 0], 20, 20)"
]
},
{
@ -49363,21 +49363,21 @@
"description": "X-axis.",
"type": "string",
"enum": [
"X"
"x"
]
},
{
"description": "Y-axis.",
"type": "string",
"enum": [
"Y"
"y"
]
},
{
"description": "Z-axis.",
"type": "string",
"enum": [
"Z"
"z"
]
},
{
@ -58824,7 +58824,7 @@
"deprecated": false,
"examples": [
"startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([10, 10], %)\n |> line([20, 10], %, \"edge1\")\n |> close(%, \"edge2\")",
"fn cube = (pos, scale) => {\n const sg = startSketchOn('XY')\n |> startProfileAt(pos, %)\n |> line([0, scale], %)\n |> line([scale, 0], %)\n |> line([0, -scale], %)\n |> close(%)\n |> extrude(scale, %)\n\n return sg\n}\n\nconst box = cube([0, 0], 20)\n\nconst part001 = startSketchOn(box, \"start\")\n |> startProfileAt([0, 0], %)\n |> line([10, 10], %)\n |> line([20, 10], %, \"edge1\")\n |> close(%)\n |> extrude(20, %)"
"fn cube = (pos, scale) => {\n const sg = startSketchOn('XY')\n |> startProfileAt(pos, %)\n |> line([0, scale], %)\n |> line([scale, 0], %)\n |> line([0, -scale], %)\n |> close(%)\n |> extrude(scale, %)\n\n return sg\n}\n\nconst box = cube([0, 0], 20)\n\nconst part001 = startSketchOn(box, \"start\")\n |> startProfileAt([0, 0], %)\n |> line([10, 10], %)\n |> line([20, 10], %, \"edge1\")\n |> close(%)\n |> extrude(20, %)"
]
},
{

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 waitOn from 'wait-on'
import { roundOff } from 'lib/utils'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { secrets } from './secrets'
import {
TEST_SETTINGS,
TEST_SETTINGS_KEY,
TEST_SETTINGS_CORRUPTED,
TEST_SETTINGS_ONBOARDING,
} from './storageStates'
import { basicStorageState } from './storageStates'
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
@ -37,25 +32,13 @@ test.beforeEach(async ({ context, page }) => {
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
await page.emulateMedia({ reducedMotion: 'reduce' })
})
test.setTimeout(60000)
test('Basic sketch', async ({ page }) => {
test('Basic sketch', async ({ page, context }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -145,7 +128,6 @@ test('Can moving camera', async ({ page, context }) => {
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openAndClearDebugPanel()
await u.closeKclCodePanel()
const camPos: [number, number, number] = [0, 85, 85]
const bakeInRetries = async (
@ -179,8 +161,6 @@ test('Can moving camera', async ({ page, context }) => {
}, 300)
await u.openAndClearDebugPanel()
await page.getByTestId('cam-x-position').isVisible()
const vals = await Promise.all([
page.getByTestId('cam-x-position').inputValue(),
page.getByTestId('cam-y-position').inputValue(),
@ -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()
})
/* Ignore this test for now since its causing engine to crash
*
* test('if your kcl gets an error from the engine it is inlined', async ({
page,
}) => {
test('executes on load', async ({ page, context }) => {
const u = getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const box = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, 10], %)
|> line([10, 0], %)
|> line([0, -10], %, 'revolveAxis')
|> close(%)
|> extrude(10, %)
const sketch001 = startSketchOn(box, "revolveAxis")
|> startProfileAt([5, 10], %)
|> line([0, -10], %)
|> line([2, 0], %)
|> line([0, 10], %)
|> close(%)
|> revolve({
axis: 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 () => {
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
@ -397,11 +325,7 @@ test('executes on load', async ({ page }) => {
await u.waitForAuthSkipAppStart()
// expand variables section
const variablesTabButton = page.getByRole('tab', {
name: 'Variables',
exact: false,
})
await variablesTabButton.click()
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
@ -416,20 +340,16 @@ test('executes on load', async ({ page }) => {
).toBeVisible()
})
test('re-executes', async ({ page }) => {
test('re-executes', async ({ page, context }) => {
const u = getUtils(page)
await page.addInitScript(async () => {
await context.addInitScript(async (token) => {
localStorage.setItem('persistCode', `const myVar = 5`)
})
await page.setViewportSize({ width: 1000, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
const variablesTabButton = page.getByRole('tab', {
name: 'Variables',
exact: false,
})
await variablesTabButton.click()
await page.getByText('Variables').click()
// expect to see "myVar:5"
await expect(
page.locator('.pretty-json-container >> text=myVar:5')
@ -446,11 +366,9 @@ test('re-executes', async ({ page }) => {
).toBeVisible()
})
const sketchOnPlaneAndBackSideTest = async (
page: any,
plane: string,
clickCoords: { x: number; y: number }
) => {
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 })
@ -458,72 +376,81 @@ const sketchOnPlaneAndBackSideTest = async (
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
let camPos: [number, number, number] = [100, 100, 100]
if (plane === '-XY' || plane === '-YZ' || plane === '-XZ') {
camPos = camCmdBackSide
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.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.updateCamPosition(viewCmd)
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 code = `const part001 = startSketchOn('${plane}')
const codeTemplate = (
plane = 'XY'
) => `const part001 = startSketchOn('${plane}')
|> startProfileAt([1.14, -1.54], %)`
await u.openDebugPanel()
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.updateCamPosition(camPos)
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(code)
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()
}
test.describe('Can create sketches on all planes and their back sides', () => {
test('XY', async ({ page }) => {
await sketchOnPlaneAndBackSideTest(
page,
'XY',
{ x: 600, y: 388 } // red plane
// { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
)
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.
})
test('YZ', async ({ page }) => {
await sketchOnPlaneAndBackSideTest(page, 'YZ', { x: 700, y: 250 }) // green plane
await TestSinglePlane({
viewCmd: camPos,
expectedCode: codeTemplate('YZ'),
clickCoords: { x: 700, y: 250 }, // green plane
})
test('XZ', async ({ page }) => {
await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 80 }) // blue plane
await TestSinglePlane({
viewCmd: camPos,
expectedCode: codeTemplate('XZ'),
clickCoords: { x: 700, y: 80 }, // blue plane
})
test('-XY', async ({ page }) => {
await sketchOnPlaneAndBackSideTest(page, '-XY', { x: 600, y: 118 }) // back of red plane
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
await TestSinglePlane({
viewCmd: camCmdBackSide,
expectedCode: codeTemplate('-XY'),
clickCoords: { x: 601, y: 118 }, // back of red plane
})
test('-YZ', async ({ page }) => {
await sketchOnPlaneAndBackSideTest(page, '-YZ', { x: 700, y: 219 }) // back of green plane
await TestSinglePlane({
viewCmd: camCmdBackSide,
expectedCode: codeTemplate('-YZ'),
clickCoords: { x: 730, y: 219 }, // back of green plane
})
test('-XZ', async ({ page }) => {
await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 427 }) // back of blue plane
await TestSinglePlane({
viewCmd: camCmdBackSide,
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
await expect(page.locator('.cm-completionLabel')).toHaveCount(3)
await page.getByText('startSketchOn').click()
await page.keyboard.type("'XZ'")
await page.keyboard.press('Tab')
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
@ -564,11 +490,7 @@ test('Auto complete works', async ({ page }) => {
await page.waitForTimeout(100)
await page.keyboard.press('Enter') // accepting the auto complete, not a new line
await page.keyboard.press('Tab')
await page.keyboard.type('12')
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')
await page.keyboard.type('([0,0], %)')
await page.keyboard.press('Enter')
await page.keyboard.type(' |> lin')
@ -579,149 +501,145 @@ test('Auto complete works', async ({ page }) => {
await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter')
// finish line with comment
await page.keyboard.type('5')
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')
await page.keyboard.type(' // lin')
await page.keyboard.type('(5, %) // lin')
await page.waitForTimeout(100)
// there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([3.14, 12], %)
.toHaveText(`const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> xLine(5, %) // lin`)
})
test('Stored settings are validated and fall back to defaults', async ({
page,
context,
}) => {
const u = getUtils(page)
// Override beforeEach test setup
// Stored settings validation test
test.describe('Settings persistence and validation tests', () => {
// Override 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 }),
}
)
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)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
test.use({ storageState })
// Check the settings were reset
const storedSettings = TOML.parse(
await page.evaluate(
({ settingsKey }) => localStorage.getItem(settingsKey) || '{}',
{ settingsKey: TEST_SETTINGS_KEY }
)
) as { settings: SaveSettingsPayload }
test('Stored settings are validated and fall back to defaults', async ({
page,
}) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
expect(storedSettings.settings.app?.theme).toBe('dark')
// Check the settings were reset
const storedSettings = TOML.parse(
await page.evaluate(() => localStorage.getItem('/user.toml') || '{}')
) as { settings: SaveSettingsPayload }
// Check that the invalid settings were removed
expect(storedSettings.settings.modeling?.defaultUnit).toBe(undefined)
expect(storedSettings.settings.modeling?.mouseControls).toBe(undefined)
expect(storedSettings.settings.app?.projectDirectory).toBe(undefined)
expect(storedSettings.settings.projects?.defaultProjectName).toBe(undefined)
expect(storedSettings.settings.app?.theme).toBe('dark')
// Check that the invalid settings were removed
expect(storedSettings.settings.modeling?.defaultUnit).toBe(undefined)
expect(storedSettings.settings.modeling?.mouseControls).toBe(undefined)
expect(storedSettings.settings.app?.projectDirectory).toBe(undefined)
expect(storedSettings.settings.projects?.defaultProjectName).toBe(undefined)
})
test('Project settings can be set and override user settings', async ({
page,
}) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
// Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('Meta+Shift+,')
await expect(
page.getByRole('heading', { name: 'Settings', exact: true })
).toBeVisible()
await page
.locator('select[name="app-theme"]')
.selectOption({ value: 'light' })
// Verify the toast appeared
await expect(
page.getByText(`Set theme to "light" for this project`)
).toBeVisible()
// Check that the theme changed
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
// Check that the user setting was not changed
await page.getByRole('radio', { name: 'User' }).click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
// Roll back to default "system" theme
await page
.getByText(
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
)
.hover()
await page
.getByRole('button', {
name: 'Roll back theme ; Has tooltip: Roll back to match default',
})
.click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
// Check that the project setting did not change
await page.getByRole('radio', { name: 'Project' }).click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
})
})
test('Project settings can be set and override user settings', async ({
page,
}) => {
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/', { waitUntil: 'domcontentloaded' })
await page
.getByRole('button', { name: 'Start Sketch' })
.waitFor({ state: 'visible' })
// Onboarding tests
test.describe('Onboarding tests', () => {
// Override test setup
const storageState = structuredClone(basicStorageState)
const s = TOML.parse(storageState.origins[0].localStorage[2].value) as {
settings: SaveSettingsPayload
}
s.settings.app.onboardingStatus = '/export'
storageState.origins[0].localStorage[2].value = TOML.stringify(s)
test.use({ storageState })
// Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('Meta+Shift+,')
test('Onboarding redirects and code updating', async ({ page, context }) => {
const u = getUtils(page)
await expect(
page.getByRole('heading', { name: 'Settings', exact: true })
).toBeVisible()
await page
.locator('select[name="app-theme"]')
.selectOption({ value: 'light' })
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
// Verify the toast appeared
await expect(
page.getByText(`Set theme to "light" for this project`)
).toBeVisible()
// Check that the theme changed
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
// Check that the user setting was not changed
await page.getByRole('radio', { name: 'User' }).click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('dark')
// Roll back to default "system" theme
await page
.getByText(
'themeRoll back themeRoll back to match defaultThe overall appearance of the appl'
// Test that the redirect happened
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
)
.hover()
await page
.getByRole('button', {
name: 'Roll back theme ; Has tooltip: Roll back to match default',
})
.click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
// Check that the project setting did not change
await page.getByRole('radio', { name: 'Project' }).click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
})
// Test that you come back to this page when you refresh
await page.reload()
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
)
test('Onboarding redirects and code updating', async ({ page }) => {
const u = getUtils(page)
// Test that the onboarding pane loaded
const title = page.locator('[data-testid="onboarding-content"]')
await expect(title).toBeAttached()
// Override beforeEach test setup
await page.addInitScript(
async ({ settingsKey, settings }) => {
// Give some initial code, so we can test that it's cleared
localStorage.setItem('persistCode', 'const sigmaAllow = 15000')
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING }),
}
)
// 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('')
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/%2Fbrowser%2Fmain.kcl/onboarding/export`
)
// Test that you come back to this page when you refresh
await page.reload()
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
)
// Test that the onboarding pane loaded
const title = page.locator('[data-testid="onboarding-content"]')
await expect(title).toBeAttached()
// Test that the code changes when you advance to the next step
await page.locator('[data-testid="onboarding-next"]').click()
await expect(page.locator('.cm-content')).toHaveText('')
// Test that the code is not empty when you click on the next step
await page.locator('[data-testid="onboarding-next"]').click()
await expect(page.locator('.cm-content')).toHaveText(/.+/)
// Test 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 }) => {
@ -933,21 +851,19 @@ test.describe('Command bar tests', () => {
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
})
test('Can extrude from the command bar', async ({ page }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const distance = sqrt(20)
const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -14.93], %)
|> line([-23.44, 0.52], %)
|> close(%)
`
)
})
// Override test setup code
const storageState = structuredClone(basicStorageState)
storageState.origins[0].localStorage[1].value = `const distance = sqrt(20)
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(%)
`
test.use({ storageState })
test('Can extrude from the command bar', async ({ page, context }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
@ -956,13 +872,15 @@ test.describe('Command bar tests', () => {
// Make sure the stream is up
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await u.clearCommandLogs()
await page.getByText('|> line([0.73, -14.93], %)').click()
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
await page.getByText('|> startProfileAt([-6.95, 4.98], %)').click()
await expect(
page.getByRole('button', { name: 'Extrude' })
).not.toBeDisabled()
let cmdSearchBar = page.getByPlaceholder('Search commands')
await page.keyboard.press('Meta+K')
@ -980,25 +898,23 @@ test.describe('Command bar tests', () => {
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
'distance001'
)
const continueButton = page.getByRole('button', { name: 'Continue' })
const submitButton = page.getByRole('button', { name: 'Submit command' })
await continueButton.click()
await expect(page.getByRole('button', { name: 'Continue' })).toBeEnabled()
await page.getByRole('button', { name: 'Continue' }).click()
// Review step and argument hotkeys
await expect(submitButton).toBeEnabled()
await expect(
page.getByRole('button', { name: 'Submit command' })
).toBeEnabled()
await page.keyboard.press('Backspace')
// Assert we're back on the distance step
await expect(
page.getByRole('button', { name: 'Distance 12', exact: false })
).toBeDisabled()
await page.keyboard.press('Enter')
await continueButton.click()
await submitButton.click()
await expect(page.getByText('Confirm Extrude')).toBeVisible()
// Check that the code was updated
await u.waitForCmdReceive('extrude')
await page.keyboard.press('Enter')
// Unfortunately this indentation seems to matter for the test
await expect(page.locator('.cm-content')).toHaveText(
`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)
await page.addInitScript(async () => {
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part = startSketchOn('XY')
@ -1151,7 +1067,7 @@ test('ProgramMemory can be serialised', async ({ page }) => {
|> line([0, -1], %)
|> close(%)
|> extrude(1, %)
|> patternLinear3d({
|> patternLinear({
axis: [1, 0, 1],
repetitions: 3,
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 ({
page,
context,
}) => {
const u = getUtils(page)
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], %)',
editOnly: '|> startProfileAt([15.79, -14.6], %)',
}
await page.addInitScript(
await context.addInitScript(
async ({
extrudeAndEditBlocked,
extrudeAndEditBlockedInFunction,
@ -1235,7 +1152,7 @@ fn yohey = (pos) => {
},
selectionsSnippets
)
await page.setViewportSize({ width: 1200, height: 1000 })
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
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()
})
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)
await page.addInitScript(async () => {
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
@ -1419,95 +1339,92 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
|> tangentialArcTo([27.6, -3.25], %)`)
})
const doSnapAtDifferentScales = async (
page: any,
camPos: [number, number, number],
scale = 1,
fudge = 0
) => {
test('Snap to close works (at any scale)', async ({ page }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
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(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
const doSnapAtDifferentScales = async (
camPos: [number, number, number],
expectedCode: string
) => {
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
await u.openAndClearDebugPanel()
await u.updateCamPosition(camPos)
await u.closeDebugPanel()
await u.openAndClearDebugPanel()
await u.updateCamPosition(camPos)
await u.closeDebugPanel()
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')`
)
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')`
)
let prevContent = await page.locator('.cm-content').innerText()
let prevContent = await page.locator('.cm-content').innerText()
const pointA = [700, 200]
const pointB = [900, 200]
const pointC = [900, 400]
const pointA = [700, 200]
const pointB = [900, 200]
const pointC = [900, 400]
// draw three lines
await page.mouse.click(pointA[0], pointA[1])
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// draw three lines
await page.mouse.click(pointA[0], pointA[1])
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await page.mouse.click(pointB[0], pointB[1])
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await page.mouse.click(pointB[0], pointB[1])
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await page.mouse.click(pointC[0], pointC[1])
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await page.mouse.click(pointC[0], pointC[1])
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await page.mouse.move(pointA[0] - 12, pointA[1] + 12)
const pointNotQuiteA = [pointA[0] - 7, pointA[1] + 7]
await page.mouse.move(pointNotQuiteA[0], pointNotQuiteA[1], { steps: 10 })
await page.mouse.move(pointA[0] - 12, pointA[1] + 12)
const pointNotQuiteA = [pointA[0] - 7, pointA[1] + 7]
await page.mouse.move(pointNotQuiteA[0], pointNotQuiteA[1], { steps: 10 })
await page.mouse.click(pointNotQuiteA[0], pointNotQuiteA[1])
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
await page.mouse.click(pointNotQuiteA[0], pointNotQuiteA[1])
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
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
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.removeCurrentCode()
}
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.removeCurrentCode()
}
test.describe('Snap to close works (at any scale)', () => {
test('[0, 100, 100]', async ({ page }) => {
await doSnapAtDifferentScales(page, [0, 100, 100], 0.01, 0.01)
})
const codeTemplate = (
scale = 1,
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(%)`
test('[0, 10000, 10000]', async ({ page }) => {
await doSnapAtDifferentScales(page, [0, 10000, 10000])
})
await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01))
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
})
test('Sketch on face', async ({ page }) => {
test('Sketch on face', async ({ page, context }) => {
const u = getUtils(page)
await page.addInitScript(async () => {
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
@ -1632,44 +1549,3 @@ test('Sketch on face', async ({ page }) => {
|> close(%)
|> 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 JSZip from 'jszip'
import path from 'path'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
import { basicSettings, basicStorageState } from './storageStates'
import * as TOML from '@iarna/toml'
test.beforeEach(async ({ page }) => {
// reducedMotion kills animations, which speeds up tests and reduces flakiness
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)
@ -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 () => {
localStorage.setItem(
'SETTINGS_PERSIST_KEY',
@ -353,8 +336,8 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
})
)
})
const code = `const part001 = startSketchOn('${plane}')
const u = getUtils(page)
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|> startProfileAt([7.00, 4.40], %)
|> line([6.60, -0.20], %)
|> line([2.80, 5.00], %)
@ -363,11 +346,9 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|> close(%)
|> extrude(10.00, %)
`
await page.addInitScript(async (code: string) => {
await context.addInitScript(async (code) => {
localStorage.setItem('persistCode', code)
})
const u = getUtils(page)
}, makeCode('XY'))
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
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.clearAndCloseDebugPanel()
await page.waitForTimeout(200)
// clear code
await u.removeCurrentCode()
await u.openAndClearDebugPanel()
await u.doAndWaitForImageDiff(
() => page.locator('.cm-content').fill(code),
200
)
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await u.closeKclCodePanel()
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
await u.openKclCodePanel()
}
const runSnapshotsForOtherPlanes = async (plane = 'XY') => {
// clear code
await u.removeCurrentCode()
// add makeCode('XZ')
await u.openAndClearDebugPanel()
await page.locator('.cm-content').fill(makeCode(plane))
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
test.describe('extrude on default planes should be stable', () => {
test('XY', async ({ page, context }) => {
await extrudeDefaultPlane(context, page, 'XY')
})
await page.getByText('Code').click()
await page.waitForTimeout(150)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
await page.getByText('Code').click()
}
await runSnapshotsForOtherPlanes('XY')
await runSnapshotsForOtherPlanes('-XY')
test('XZ', async ({ page, context }) => {
await extrudeDefaultPlane(context, page, 'XZ')
})
await runSnapshotsForOtherPlanes('XZ')
await runSnapshotsForOtherPlanes('-XZ')
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')
})
await runSnapshotsForOtherPlanes('YZ')
await runSnapshotsForOtherPlanes('-YZ')
})
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)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -508,126 +475,75 @@ test('Draft rectangles should look right', async ({ page, context }) => {
)
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
await u.closeDebugPanel()
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100)
// Equip the rectangle tool
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Rectangle' }).click()
await u.closeDebugPanel()
// 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 })
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
// Ensure the draft rectangle looks the same as it usually does
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)
|> tangentialArcTo([27.34, -3.08], %)`)
// click tangential arc tool again to unequip it
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
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
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)
|> tangentialArcTo([27.34, -3.08], %)`)
// click tangential arc tool again to unequip it
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
test.describe('Client side scene scale should match engine scale - Millimeters', () => {
const storageState = structuredClone(basicStorageState)
storageState.origins[0].localStorage[2].value = TOML.stringify({
settings: {
...basicSettings,
modeling: {
...basicSettings.modeling,
defaultUnit: 'mm',
},
},
})
test.use({
storageState,
})
test('Millimeter scale', async ({ page }) => {
await page.addInitScript(
async ({ settingsKey, settings }) => {
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({
settings: {
...TEST_SETTINGS,
modeling: {
...TEST_SETTINGS.modeling,
defaultUnit: 'mm',
},
},
}),
}
)
test('Millimeters', async ({ page }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -722,7 +638,7 @@ test('Sketch on face with none z-up', async ({ page, context }) => {
|> close(%)
|> extrude(5 + 7, %)
const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([8, 8], %)
|> startProfileAt([-2.89, 1.82], %)
|> line([4.68, 3.05], %)
|> line([0, -7.79], %, 'seg02')
|> close(%)
@ -734,19 +650,6 @@ const part002 = startSketchOn(part001, 'seg01')
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
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(
page.getByRole('button', { name: 'Start Sketch' })
).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 { secrets } from './secrets'
import * as TOML from '@iarna/toml'
import { Themes } from 'lib/theme'
export const TEST_SETTINGS_KEY = '/user.toml'
export const TEST_SETTINGS = {
export const basicSettings = {
app: {
theme: Themes.Dark,
onboardingStatus: 'dismissed',
@ -21,26 +22,19 @@ export const TEST_SETTINGS = {
},
} satisfies Partial<SaveSettingsPayload>
export const TEST_SETTINGS_ONBOARDING = {
...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export ' },
} satisfies Partial<SaveSettingsPayload>
export const TEST_SETTINGS_CORRUPTED = {
app: {
theme: Themes.Dark,
onboardingStatus: 'dismissed',
projectDirectory: 123 as any,
},
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>
export const basicStorageState = {
cookies: [],
origins: [
{
origin: 'http://localhost:3000',
localStorage: [
{ name: 'TOKEN_PERSIST_KEY', value: secrets.token },
{ name: 'persistCode', value: '' },
{
name: '/user.toml',
value: TOML.stringify({ settings: basicSettings }),
},
],
},
],
}

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

View File

@ -3,34 +3,33 @@
"version": "0.17.3",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.16.0",
"@codemirror/autocomplete": "^6.15.0",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.18",
"@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",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.1",
"@tauri-apps/api": "2.0.0-beta.8",
"@replit/codemirror-interact": "^6.3.0",
"@tauri-apps/api": "^2.0.0-beta.7",
"@tauri-apps/plugin-dialog": "^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-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-updater": "^2.0.0-beta.2",
"@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",
"@ts-stack/markdown": "^1.5.0",
"@tweenjs/tween.js": "^23.1.1",
"@types/node": "^18.19.31",
"@types/react": "^18.2.77",
"@types/react-dom": "^18.2.25",
"@types/node": "^18.19.26",
"@types/react": "^18.2.75",
"@types/react-dom": "^18.2.22",
"@uiw/react-codemirror": "^4.21.25",
"@xstate/inspect": "^0.8.0",
"@xstate/react": "^3.2.2",
@ -39,7 +38,6 @@
"decamelize": "^6.0.0",
"formik": "^2.4.5",
"fuse.js": "^7.0.0",
"html2canvas-pro": "^1.4.3",
"http-server": "^14.1.1",
"json-rpc-2.0": "^1.6.0",
"jszip": "^3.10.1",
@ -54,13 +52,13 @@
"react-modal-promise": "^1.0.2",
"react-router-dom": "^6.22.3",
"sketch-helpers": "^0.0.4",
"swr": "^2.2.5",
"three": "^0.163.0",
"swr": "^2.2.2",
"three": "^0.160.0",
"toml": "^3.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"ua-parser-js": "^1.0.37",
"typescript": "^5.4.3",
"uuid": "^9.0.1",
"vitest": "^1.5.0",
"vitest": "^1.4.0",
"vscode-jsonrpc": "^8.1.0",
"vscode-languageserver-protocol": "^3.17.5",
"wasm-pack": "^0.12.1",
@ -117,26 +115,24 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.24.3",
"@iarna/toml": "^2.2.5",
"@playwright/test": "^1.43.1",
"@tauri-apps/cli": "^2.0.0-beta.13",
"@playwright/test": "^1.39.0",
"@tauri-apps/cli": "^2.0.0-beta.12",
"@types/crypto-js": "^4.2.2",
"@types/debounce-promise": "^3.1.9",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4",
"@types/react-modal": "^3.16.3",
"@types/three": "^0.163.0",
"@types/ua-parser-js": "^0.7.39",
"@types/three": "^0.160.0",
"@types/uuid": "^9.0.8",
"@types/wait-on": "^5.3.4",
"@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.10",
"@vitejs/plugin-react": "^4.2.1",
"@wdio/cli": "^8.24.3",
"@wdio/globals": "^8.36.0",
"@wdio/local-runner": "^8.36.0",
"@wdio/mocha-framework": "^8.36.0",
"@wdio/spec-reporter": "^8.36.0",
"@wdio/globals": "^8.24.3",
"@wdio/local-runner": "^8.35.1",
"@wdio/mocha-framework": "^8.35.0",
"@wdio/spec-reporter": "^8.32.4",
"@xstate/cli": "^0.5.17",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
@ -151,12 +147,12 @@
"prettier": "^2.8.0",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.4.1",
"vite": "^5.2.9",
"vite": "^5.2.6",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2",
"vitest-webgl-canvas-mock": "^1.1.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 { basicStorageState } from './e2e/playwright/storageStates'
/**
* 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 */
trace: 'on-first-retry',
/* Use a common shared localStorage */
storageState: basicStorageState,
},
/* 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
[build-dependencies]
tauri-build = { version = "2.0.0-beta.12", features = [] }
tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.2.67"
kittycad = "0.2.63"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
tauri-plugin-dialog = { version = "2.0.0-beta.5" }
tauri-plugin-fs = { version = "2.0.0-beta.5" }
tauri-plugin-http = { version = "2.0.0-beta.5" }
tauri-plugin-os = { version = "2.0.0-beta.2" }
tauri-plugin-process = { version = "2.0.0-beta.2" }
tauri-plugin-shell = { version = "2.0.0-beta.2" }
tauri-plugin-updater = { version = "2.0.0-beta.4" }
tauri = { version = "2.0.0-beta", features = [ "devtools", "unstable"] }
tauri-plugin-dialog = { version = "2.0.0-beta.0" }
tauri-plugin-fs = { version = "2.0.0-beta.0" }
tauri-plugin-http = { version = "2.0.0-beta.0" }
tauri-plugin-os = { version = "2.0.0-beta.0" }
tauri-plugin-shell = { version = "2.0.0-beta.0" }
tauri-plugin-updater = { version = "2.0.0-beta.0" }
tokio = { version = "1.37.0", features = ["time"] }
toml = "0.8.2"

View File

@ -77,9 +77,7 @@
"os:allow-arch",
"os:allow-exe-extension",
"os:allow-locale",
"os:allow-hostname",
"process:allow-restart",
"updater:default"
"os:allow-hostname"
],
"platforms": [
"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

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