Compare commits

..

2 Commits

Author SHA1 Message Date
55a6155400 WIP: Start using the recursion crate 2024-08-19 11:09:37 -04:00
d41972ee4d Add recursion dependency 2024-08-19 11:09:37 -04:00
122 changed files with 768 additions and 2594 deletions

View File

@ -25,9 +25,7 @@
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
"rules": {
"@typescript-eslint/no-floating-promises": "warn",
"suggest-no-throw/suggest-no-throw": "off",
"testing-library/prefer-screen-queries": "off",
"jest/valid-expect": "off"
"testing-library/prefer-screen-queries": "off"
}
},
{

View File

@ -345,7 +345,7 @@ jobs:
cat last_download.json
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.5'
uses: 'google-github-actions/auth@v2.1.3'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
@ -355,7 +355,7 @@ jobs:
project_id: kittycadapi
- name: Upload release files to public bucket
uses: google-github-actions/upload-cloud-storage@v2.1.3
uses: google-github-actions/upload-cloud-storage@v2.1.0
with:
path: artifact
glob: '*/Zoo*'
@ -363,13 +363,13 @@ jobs:
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
- name: Upload update endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.1.3
uses: google-github-actions/upload-cloud-storage@v2.1.0
with:
path: last_update.json
destination: ${{ env.BUCKET_DIR }}
- name: Upload download endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.1.3
uses: google-github-actions/upload-cloud-storage@v2.1.0
with:
path: last_download.json
destination: ${{ env.BUCKET_DIR }}

View File

@ -44,8 +44,6 @@ jobs:
- run: yarn build:wasm
- run: yarn xstate:typegen
- run: yarn tsc
- name: Lint
run: yarn eslint --max-warnings 0 src e2e
check-typos:

View File

@ -7,7 +7,6 @@ on:
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- 'src/wasm-lib/**.kcl'
- .github/workflows/cargo-test.yml
pull_request:
@ -16,7 +15,6 @@ on:
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- 'src/wasm-lib/**.kcl'
- .github/workflows/cargo-test.yml
workflow_dispatch:
permissions: read-all

View File

@ -1,31 +0,0 @@
name: Label Issues
on:
issues:
types: [opened]
permissions:
issues: write
jobs:
label:
runs-on: ubuntu-latest
steps:
- name: Check if issue opener is ZooSpiritWolf
id: check_opener
uses: actions/github-script@v7
with:
script: |
const issueOpener = context.payload.issue.user.login;
return issueOpener === 'ZooSpiritWolf';
- name: Add labels
if: steps.check_opener.outputs.result == 'true'
uses: actions/github-script@v7
with:
script: |
github.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
labels: ['bug', 'regression', 'high-priority']
});

View File

@ -346,7 +346,7 @@ jobs:
run: yarn build:wasm
- name: build electron
shell: bash
run: yarn tron:package
run: yarn electron:package
- uses: actions/download-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }}
continue-on-error: true

View File

@ -101,7 +101,7 @@ This will start the application and hot-reload on changed.
Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
To build, run `yarn tron:package`.
To build, run `yarn electron:package`.
## Checking out commits / Bisecting

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,8 @@
import { test, expect } from '@playwright/test'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import { getUtils, setup, tearDown } from './test-utils'
import { bracket } from 'lib/exampleKcl'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
import fsp from 'fs/promises'
test.beforeEach(async ({ context, page }) => {
await setup(context, page)
@ -84,7 +83,7 @@ test.describe('Code pane and errors', () => {
// error text on hover
await page.hover('.cm-lint-marker-error')
await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
await expect(page.getByText('Unexpected token').first()).toBeVisible()
// Close the code pane
await codePaneButton.click()
@ -107,7 +106,7 @@ test.describe('Code pane and errors', () => {
// error text on hover
await page.hover('.cm-lint-marker-error')
await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
await expect(page.getByText('Unexpected token').first()).toBeVisible()
})
test('When error is not in view you can click the badge to scroll to it', async ({
@ -218,93 +217,3 @@ test.describe('Code pane and errors', () => {
).toBeVisible()
})
})
test(
'Opening multiple panes persists when switching projects',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
// Setup multiple projects.
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await Promise.all([
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
])
await Promise.all([
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/router-template-slate/main.kcl`
),
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
),
])
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await test.step('Opening the bracket project should load', async () => {
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
})
// If they're open by default, we're not actually testing anything.
await test.step('Pre-condition: panes are not already visible', async () => {
await expect(page.locator('#variables-pane')).not.toBeVisible()
await expect(page.locator('#logs-pane')).not.toBeVisible()
})
await test.step('Open multiple panes', async () => {
await u.openKclCodePanel()
await u.openVariablesPane()
await u.openLogsPane()
})
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
await page.getByTestId('app-logo').click()
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
})
await test.step('Opening the router-template project should load', async () => {
await expect(page.getByText('router-template-slate')).toBeVisible()
await page.getByText('router-template-slate').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
})
await test.step('All panes opened before should be visible', async () => {
await expect(page.locator('#code-pane')).toBeVisible()
await expect(page.locator('#variables-pane')).toBeVisible()
await expect(page.locator('#logs-pane')).toBeVisible()
})
await electronApp.close()
}
)

View File

@ -9,7 +9,6 @@ test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test.describe('Copilot ghost text', () => {
// eslint-disable-next-line jest/valid-title
test.skip(true, 'Needs to get covered again')
test('completes code in empty file', async ({ page }) => {

View File

@ -1,188 +0,0 @@
import { test, expect } from '@playwright/test'
import { getUtils, setupElectron, tearDown } from './test-utils'
import fsp from 'fs/promises'
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test(
'export works on the first try',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await Promise.all([fsp.mkdir(`${dir}/bracket`, { recursive: true })])
await Promise.all([
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/bracket/other.kcl`
),
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
),
])
},
})
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await test.step('on open of project', async () => {
await expect(page.getByText(`bracket`)).toBeVisible()
// open the project
await page.getByText(`bracket`).click()
// wait for the project to load
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
// expect zero errors in guter
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// export the model
const exportButton = page.getByTestId('export-pane-button')
await expect(exportButton).toBeVisible()
const gltfOption = page.getByText('glTF')
const submitButton = page.getByText('Confirm Export')
const exportingToastMessage = page.getByText(`Exporting...`)
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// Click the export button
await exportButton.click()
await expect(gltfOption).toBeVisible()
await expect(page.getByText('STL')).toBeVisible()
await page.keyboard.press('Enter')
// Click the checkbox
await expect(submitButton).toBeVisible()
await page.waitForTimeout(500)
await page.keyboard.press('Enter')
// Find the toast.
// Look out for the toast message
await expect(exportingToastMessage).toBeVisible()
await expect(alreadyExportingToastMessage).not.toBeVisible()
// Expect it to succeed.
await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`)
await expect(successToastMessage).toBeVisible()
await expect(exportingToastMessage).not.toBeVisible()
await test.step('Check the export size', async () => {
await expect
.poll(
async () => {
try {
const outputGltf = await fsp.readFile('output.gltf')
return outputGltf.byteLength
} catch (e) {
return 0
}
},
{ timeout: 15_000 }
)
.toBe(477327)
// clean up output.gltf
await fsp.rm('output.gltf')
})
})
await test.step('on open of file in file pane', async () => {
const u = await getUtils(page)
await u.openFilePanel()
const otherKclButton = page.getByRole('button', { name: 'other.kcl' })
// Click the file
await otherKclButton.click()
// Close the file pane
await u.closeFilePanel()
// wait for it to finish executing (todo: make this more robust)
await page.waitForTimeout(1000)
// expect zero errors in guter
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// export the model
const exportButton = page.getByTestId('export-pane-button')
await expect(exportButton).toBeVisible()
const gltfOption = page.getByText('glTF')
const submitButton = page.getByText('Confirm Export')
const exportingToastMessage = page.getByText(`Exporting...`)
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// Click the export button
await exportButton.click()
await expect(gltfOption).toBeVisible()
await expect(page.getByText('STL')).toBeVisible()
await page.keyboard.press('Enter')
// Click the checkbox
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
// Find the toast.
// Look out for the toast message
await expect(exportingToastMessage).toBeVisible()
await expect(alreadyExportingToastMessage).not.toBeVisible()
// Expect it to succeed.
await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`)
await expect(successToastMessage).toBeVisible()
await expect(exportingToastMessage).not.toBeVisible()
await test.step('Check the export size', async () => {
await expect
.poll(
async () => {
try {
const outputGltf = await fsp.readFile('output.gltf')
return outputGltf.byteLength
} catch (e) {
return 0
}
},
{ timeout: 15_000 }
)
.toBe(105022)
// clean up output.gltf
await fsp.rm('output.gltf')
})
await electronApp.close()
})
await electronApp.close()
}
)

View File

@ -354,7 +354,7 @@ test.describe('Editor tests', () => {
// error text on hover
await page.hover('.cm-lint-marker-error')
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
await expect(page.getByText('Unexpected token').first()).toBeVisible()
// select the line that's causing the error and delete it
await page.getByText('$ error').click()
@ -714,15 +714,17 @@ test.describe('Editor tests', () => {
|> close(%)`)
})
test('Can undo a sketch modification with ctrl+z', async ({ page }) => {
// failing for the same reason as "Can edit a sketch that has been extruded in the same pipe"
// please fix together
test.fixme('Can undo a sketch modification with ctrl+z', async ({ page }) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -10.01], %)
|> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -0.38], %)
|> tangentialArcTo([24.95, -5.38], %)
|> close(%)
|> extrude(5, %)`
)
@ -757,11 +759,11 @@ test.describe('Editor tests', () => {
})
await page.waitForTimeout(100)
const startPX = [665, 397]
const startPX = [665, 458]
const dragPX = 40
await page.getByText('startProfileAt([4.61, -10.01], %)').click()
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible()
@ -799,7 +801,7 @@ test.describe('Editor tests', () => {
// drag tangentialArcTo handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 },
targetPosition: {
x: tangentEnd.x + dragPX,
y: tangentEnd.y + dragPX,
@ -811,12 +813,12 @@ test.describe('Editor tests', () => {
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.12, -12.68], %)
|> line([15.39, -2.78], %)
|> tangentialArcTo([27.6, -3.05], %)
|> close(%)
|> extrude(5, %)
`)
|> startProfileAt([7.12, -16.82], %)
|> line([15.4, -2.74], %)
|> tangentialArcTo([24.95, -5.38], %)
|> line([2.65, -2.69], %)
|> close(%)
|> extrude(5, %)`)
// Hit undo
await page.keyboard.down('Control')
@ -825,11 +827,11 @@ test.describe('Editor tests', () => {
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.12, -12.68], %)
|> line([15.39, -2.78], %)
|> tangentialArcTo([24.95, -0.38], %)
|> close(%)
|> extrude(5, %)`)
|> startProfileAt([7.12, -16.82], %)
|> line([15.4, -2.74], %)
|> tangentialArcTo([24.95, -5.38], %)
|> close(%)
|> extrude(5, %)`)
// Hit undo again.
await page.keyboard.down('Control')
@ -838,12 +840,11 @@ test.describe('Editor tests', () => {
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.12, -12.68], %)
|> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -0.38], %)
|> close(%)
|> extrude(5, %)
`)
|> startProfileAt([7.12, -16.82], %)
|> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -5.38], %)
|> close(%)
|> extrude(5, %)`)
// Hit undo again.
await page.keyboard.down('Control')
@ -853,9 +854,9 @@ test.describe('Editor tests', () => {
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -10.01], %)
|> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -0.38], %)
|> tangentialArcTo([24.95, -5.38], %)
|> close(%)
|> extrude(5, %)`)
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -1,102 +0,0 @@
import { test, expect } from '@playwright/test'
import { setupElectron, tearDown } from './test-utils'
import fsp from 'fs/promises'
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test(
'When machine-api server not found butt is disabled and shows the reason',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
)
},
})
await page.setViewportSize({ width: 1200, height: 500 })
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
const notFoundText = 'Machine API server was not discovered'
await expect(page.getByText(notFoundText).first()).not.toBeVisible()
// Find the make button
const makeButton = page.getByRole('button', { name: 'Make' })
// Make sure the button is visible but disabled
await expect(makeButton).toBeVisible()
await expect(makeButton).toBeDisabled()
// When you hover over the button, the tooltip should show
// that the machine-api server is not found
await makeButton.hover()
await expect(page.getByText(notFoundText).first()).toBeVisible()
await electronApp.close()
}
)
test(
'When machine-api server not found home screen & project status shows the reason',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
)
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const notFoundText = 'Machine API server was not discovered'
await expect(page.getByText(notFoundText)).not.toBeVisible()
const networkMachineToggle = page.getByTestId('network-machine-toggle')
await networkMachineToggle.hover()
await expect(page.getByText(notFoundText)).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(page.getByText(notFoundText).nth(1)).not.toBeVisible()
await networkMachineToggle.hover()
await expect(page.getByText(notFoundText).nth(1)).toBeVisible()
await electronApp.close()
}
)

View File

@ -347,10 +347,6 @@ test(
'Restarting onboarding on desktop takes one attempt',
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {

View File

@ -2,7 +2,6 @@ import { test, expect } from '@playwright/test'
import {
doExport,
getUtils,
isOutOfViewInScrollContainer,
Paths,
setupElectron,
tearDown,
@ -10,45 +9,15 @@ import {
import fsp from 'fs/promises'
import fs from 'fs'
import { join } from 'path'
import { FILE_EXT } from 'lib/constants'
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test(
'click help/keybindings from home page',
'Can export from electron app',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async () => {},
})
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// click ? button
await page.getByTestId('help-button').click()
await expect(page.getByTestId('keybindings-button')).toBeVisible()
// Click keyboard shortcuts button.
await page.getByTestId('keybindings-button').click()
// Make sure the keyboard shortcuts modal is visible.
await expect(page.getByText('Enter Sketch Mode')).toBeVisible()
await electronApp.close()
}
)
test(
'click help/keybindings from project page',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
@ -61,181 +30,82 @@ test(
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
page.on('console', console.log)
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
await electronApp.context().addInitScript(async () => {
;(window as any).playwrightSkipFilePicker = true
})
// click ? button
await page.getByTestId('help-button').click()
await expect(page.getByTestId('keybindings-button')).toBeVisible()
// Click keyboard shortcuts button.
await page.getByTestId('keybindings-button').click()
// Make sure the keyboard shortcuts modal is visible.
await expect(page.getByText('Enter Sketch Mode')).toBeVisible()
const pointOnModel = { x: 630, y: 280 }
await electronApp.close()
}
)
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
test(
'when code with error first loads you get errors in console',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/broken-code`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/broken-code-test.kcl',
`${dir}/broken-code/main.kcl`
)
},
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [75, 75, 75]), {
timeout: 10_000,
})
.toBeLessThan(10)
})
await page.setViewportSize({ width: 1200, height: 500 })
await expect(page.getByText('broken-code')).toBeVisible()
await page.getByText('broken-code').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
await electronApp.close()
}
)
test.describe('Can export from electron app', () => {
const exportMethods = ['sidebarButton', 'commandBar'] as const
for (const method of exportMethods) {
test(
`Can export using ${method}`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
)
const exportLocations: Array<Paths> = []
await test.step('export the model as a glTF', async () => {
exportLocations.push(
await doExport(
{
type: 'gltf',
storage: 'embedded',
presentation: 'pretty',
},
})
page,
true
)
)
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
await test.step('Check the export size', async () => {
await expect
.poll(
async () => {
try {
const outputGltf = await fsp.readFile('output.gltf')
return outputGltf.byteLength
} catch (e) {
return 0
}
},
{ timeout: 15_000 }
)
.toBe(477327)
page.on('console', console.log)
await electronApp.context().addInitScript(async () => {
;(window as any).playwrightSkipFilePicker = true
})
// clean up output.gltf
await fsp.rm('output.gltf')
})
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [75, 75, 75]), {
timeout: 10_000,
})
.toBeLessThan(10)
})
const exportLocations: Array<Paths> = []
await test.step(`export the model as a glTF using ${method}`, async () => {
exportLocations.push(
await doExport(
{
type: 'gltf',
storage: 'embedded',
presentation: 'pretty',
},
page,
method
)
)
})
await test.step('Check the export size', async () => {
await expect
.poll(
async () => {
try {
const outputGltf = await fsp.readFile('output.gltf')
return outputGltf.byteLength
} catch (e) {
return 0
}
},
{ timeout: 15_000 }
)
.toBe(477327)
// clean up output.gltf
await fsp.rm('output.gltf')
})
await electronApp.close()
}
)
await electronApp.close()
}
})
)
test(
'Rename and delete projects, also spam arrow keys when renaming',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
@ -524,10 +394,6 @@ test(
'Deleting projects, can delete individual project, can still create projects after deleting all',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
})
@ -622,10 +488,6 @@ test(
'Can sort projects on home page',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
})
@ -748,10 +610,6 @@ test(
'When the project folder is empty, user can create new project and open it.',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({ testInfo })
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
@ -836,10 +694,6 @@ test(
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
@ -1053,10 +907,6 @@ test(
'Search projects on desktop home',
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const projectData = [
['basic bracket', 'focusrite_scarlett_mounting_braket.kcl'],
['basic-cube', 'basic_fillet_cube_end.kcl'],
@ -1114,198 +964,10 @@ test(
}
)
test(
'file pane is scrollable when there are many files',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/testProject`, { recursive: true })
const fileNames = [
'angled_line.kcl',
'basic_fillet_cube_close_opposite.kcl',
'basic_fillet_cube_end.kcl',
'basic_fillet_cube_next_adjacent.kcl',
'basic_fillet_cube_previous_adjacent.kcl',
'basic_fillet_cube_start.kcl',
'big_number_angle_to_match_length_x.kcl',
'big_number_angle_to_match_length_y.kcl',
'close_arc.kcl',
'computed_var.kcl',
'cube-embedded.gltf',
'cube.bin',
'cube.glb',
'cube.gltf',
'cube.kcl',
'cube.mtl',
'cube.obj',
'cylinder.kcl',
'dimensions_match.kcl',
'extrude-custom-plane.kcl',
'extrude-inside-fn-with-tags.kcl',
'fillet-and-shell.kcl',
'fillet_duplicate_tags.kcl',
'focusrite_scarlett_mounting_braket.kcl',
'function_sketch.kcl',
'function_sketch_with_position.kcl',
'global-tags.kcl',
'helix_ccw.kcl',
'helix_defaults.kcl',
'helix_defaults_negative_extrude.kcl',
'helix_with_length.kcl',
'i_shape.kcl',
'kittycad_svg.kcl',
'lego.kcl',
'math.kcl',
'member_expression_sketch_group.kcl',
'mike_stress_test.kcl',
'negative_args.kcl',
'order-sketch-extrude-in-order.kcl',
'order-sketch-extrude-out-of-order.kcl',
'parametric.kcl',
'parametric_with_tan_arc.kcl',
'pattern_vase.kcl',
'pentagon_fillet_sugar.kcl',
'pipe_as_arg.kcl',
'pipes_on_pipes.kcl',
'riddle.kcl',
'riddle_small.kcl',
'router-template-slate.kcl',
'scoped-tags.kcl',
'server-rack-heavy.kcl',
'server-rack-lite.kcl',
'sketch_on_face.kcl',
'sketch_on_face_circle_tagged.kcl',
'sketch_on_face_end.kcl',
'sketch_on_face_end_negative_extrude.kcl',
'sketch_on_face_start.kcl',
'tan_arc_x_line.kcl',
'tangential_arc.kcl',
]
for (const fileName of fileNames) {
await fsp.copyFile(
`src/wasm-lib/tests/executor/inputs/${fileName}`,
`${dir}/testProject/${fileName}`
)
}
},
})
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await test.step('setup, open file pane', async () => {
await page.getByText('testProject').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await page.getByTestId('files-pane-button').click()
})
await test.step('check the last file is out of view initially, and can be scrolled to', async () => {
const element = page.getByText('tangential_arc.kcl')
const container = page.getByTestId('file-pane-scroll-container')
await expect(await isOutOfViewInScrollContainer(element, container)).toBe(
true
)
await element.scrollIntoViewIfNeeded()
await expect(await isOutOfViewInScrollContainer(element, container)).toBe(
false
)
})
await electronApp.close()
}
)
test(
'select all in code editor does not actually select all, just what is visible (regression)',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
// src/wasm-lib/tests/executor/inputs/mike_stress_test.kcl
const name = 'mike_stress_test'
await fsp.mkdir(`${dir}/${name}`, { recursive: true })
await fsp.copyFile(
`src/wasm-lib/tests/executor/inputs/${name}.kcl`,
`${dir}/${name}/main.kcl`
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await page.getByText('mike_stress_test').click()
const modifier =
process.platform === 'win32' || process.platform === 'linux'
? 'Control'
: 'Meta'
await test.step('select all in code editor, check its length', async () => {
await u.codeLocator.click()
// expect u.codeLocator to have some text
await expect(u.codeLocator).toContainText('line(')
await page.keyboard.down(modifier)
await page.keyboard.press('KeyA')
await page.keyboard.up(modifier)
// check the length of the selected text
const selectedText = await page.evaluate(() => {
const selection = window.getSelection()
return selection ? selection.toString() : ''
})
// even though if the user copied the text into their clipboard they would get the full text
// it seems that the selection is limited to what is visible
// we just want to check we did select something, and later we've verify it's empty
expect(selectedText.length).toBeGreaterThan(10)
})
await test.step('delete all the text, select again and verify there are no characters left', async () => {
await page.keyboard.press('Backspace')
await page.keyboard.down(modifier)
await page.keyboard.press('KeyA')
await page.keyboard.up(modifier)
// check the length of the selected text
const selectedText = await page.evaluate(() => {
const selection = window.getSelection()
return selection ? selection.toString() : ''
})
expect(selectedText.length).toBe(0)
await expect(u.codeLocator).toHaveText('')
})
await electronApp.close()
}
)
test(
'Settings persist across restarts',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
await test.step('We can change a user setting like theme', async () => {
const { electronApp, page } = await setupElectron({
testInfo,
@ -1344,369 +1006,3 @@ test(
})
}
)
test.describe('Renaming in the file tree', () => {
test(
'A file you have open',
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
const exampleDir = join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs'
)
await fsp.copyFile(
join(exampleDir, 'basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
join(dir, 'Test Project', 'fileToRename.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectLink = page.getByText('Test Project')
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const fileToRename = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
const renamedFile = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'newFileName.kcl' }) })
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
const renameInput = page.getByPlaceholder('fileToRename.kcl')
const newFileName = 'newFileName'
const codeLocator = page.locator('.cm-content')
await test.step('Open project and file pane', async () => {
await expect(projectLink).toBeVisible()
await projectLink.click()
await expect(projectMenuButton).toBeVisible()
await expect(projectMenuButton).toContainText('main.kcl')
await u.openFilePanel()
await expect(fileToRename).toBeVisible()
await fileToRename.click()
await expect(projectMenuButton).toContainText('fileToRename.kcl')
await u.openKclCodePanel()
await expect(codeLocator).toContainText('circle(')
await u.closeKclCodePanel()
})
await test.step('Rename the file', async () => {
await fileToRename.click({ button: 'right' })
await renameMenuItem.click()
await expect(renameInput).toBeVisible()
await renameInput.fill(newFileName)
await page.keyboard.press('Enter')
})
await test.step('Verify the file is renamed', async () => {
await expect(fileToRename).not.toBeAttached()
await expect(renamedFile).toBeVisible()
})
await test.step('Verify we navigated', async () => {
await expect(projectMenuButton).toContainText(newFileName + FILE_EXT)
const url = page.url()
expect(url).toContain(newFileName)
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
await expect(projectMenuButton).not.toContainText('main.kcl')
expect(url).not.toContain('fileToRename.kcl')
expect(url).not.toContain('main.kcl')
await u.openKclCodePanel()
await expect(codeLocator).toContainText('circle(')
})
await electronApp.close()
}
)
test(
'A file you do not have open',
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
const exampleDir = join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs'
)
await fsp.copyFile(
join(exampleDir, 'basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
join(dir, 'Test Project', 'fileToRename.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const newFileName = 'newFileName'
const projectLink = page.getByText('Test Project')
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const fileToRename = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
const renamedFile = page.getByRole('listitem').filter({
has: page.getByRole('button', { name: newFileName + FILE_EXT }),
})
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
const renameInput = page.getByPlaceholder('fileToRename.kcl')
const codeLocator = page.locator('.cm-content')
await test.step('Open project and file pane', async () => {
await expect(projectLink).toBeVisible()
await projectLink.click()
await expect(projectMenuButton).toBeVisible()
await expect(projectMenuButton).toContainText('main.kcl')
await u.openFilePanel()
await expect(fileToRename).toBeVisible()
})
await test.step('Rename the file', async () => {
await fileToRename.click({ button: 'right' })
await renameMenuItem.click()
await expect(renameInput).toBeVisible()
await renameInput.fill(newFileName)
await page.keyboard.press('Enter')
})
await test.step('Verify the file is renamed', async () => {
await expect(fileToRename).not.toBeAttached()
await expect(renamedFile).toBeVisible()
})
await test.step('Verify we have not navigated', async () => {
await expect(projectMenuButton).toContainText('main.kcl')
await expect(projectMenuButton).not.toContainText(
newFileName + FILE_EXT
)
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
const url = page.url()
expect(url).toContain('main.kcl')
expect(url).not.toContain(newFileName)
expect(url).not.toContain('fileToRename.kcl')
await u.openKclCodePanel()
await expect(codeLocator).toContainText('fillet(')
})
await electronApp.close()
}
)
test(
`A folder you're not inside`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
recursive: true,
})
const exampleDir = join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs'
)
await fsp.copyFile(
join(exampleDir, 'basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectLink = page.getByText('Test Project')
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const folderToRename = page.getByRole('button', {
name: 'folderToRename',
})
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
const renameInput = page.getByPlaceholder('folderToRename')
const newFolderName = 'newFolderName'
await test.step('Open project and file pane', async () => {
await expect(projectLink).toBeVisible()
await projectLink.click()
await expect(projectMenuButton).toBeVisible()
await expect(projectMenuButton).toContainText('main.kcl')
const url = page.url()
expect(url).toContain('main.kcl')
expect(url).not.toContain('folderToRename')
await u.openFilePanel()
await expect(folderToRename).toBeVisible()
})
await test.step('Rename the folder', async () => {
await folderToRename.click({ button: 'right' })
await expect(renameMenuItem).toBeVisible()
await renameMenuItem.click()
await expect(renameInput).toBeVisible()
await renameInput.fill(newFolderName)
await page.keyboard.press('Enter')
})
await test.step('Verify the folder is renamed, and no navigation occurred', async () => {
const url = page.url()
expect(url).toContain('main.kcl')
expect(url).not.toContain('folderToRename')
await expect(projectMenuButton).toContainText('main.kcl')
await expect(renamedFolder).toBeVisible()
await expect(folderToRename).not.toBeAttached()
})
await electronApp.close()
}
)
test(
`A folder you are inside`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const exampleDir = join('src', 'wasm-lib', 'tests', 'executor', 'inputs')
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
recursive: true,
})
await fsp.copyFile(
join(exampleDir, 'basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectLink = page.getByText('Test Project')
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const folderToRename = page.getByRole('button', {
name: 'folderToRename',
})
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
const fileWithinFolder = page.getByRole('listitem').filter({
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
})
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
const renameInput = page.getByPlaceholder('folderToRename')
const newFolderName = 'newFolderName'
await test.step('Open project and navigate into folder', async () => {
await expect(projectLink).toBeVisible()
await projectLink.click()
await expect(projectMenuButton).toBeVisible()
await expect(projectMenuButton).toContainText('main.kcl')
const url = page.url()
expect(url).toContain('main.kcl')
expect(url).not.toContain('folderToRename')
await u.openFilePanel()
await expect(folderToRename).toBeVisible()
await folderToRename.click()
await expect(fileWithinFolder).toBeVisible()
await fileWithinFolder.click()
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
const newUrl = page.url()
expect(newUrl).toContain('folderToRename')
expect(newUrl).toContain('someFileWithin.kcl')
expect(newUrl).not.toContain('main.kcl')
})
await test.step('Rename the folder', async () => {
await folderToRename.click({ button: 'right' })
await expect(renameMenuItem).toBeVisible()
await renameMenuItem.click()
await expect(renameInput).toBeVisible()
await renameInput.fill(newFolderName)
await page.keyboard.press('Enter')
})
await test.step('Verify the folder is renamed, and navigated to new path', async () => {
const urlSnippet = encodeURIComponent(
join(newFolderName, 'someFileWithin.kcl')
)
await page.waitForURL(new RegExp(urlSnippet))
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
await expect(renamedFolder).toBeVisible()
await expect(folderToRename).not.toBeAttached()
// URL is synchronous, so we check the other stuff first
const url = page.url()
expect(url).not.toContain('main.kcl')
expect(url).toContain(newFolderName)
expect(url).toContain('someFileWithin.kcl')
})
await electronApp.close()
}
)
})

View File

@ -194,7 +194,7 @@ const sketch001 = startSketchAt([-0, -0])
// error text on hover
await page.hover('.cm-lint-marker-error')
await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
await expect(page.getByText('Unexpected token').first()).toBeVisible()
// Okay execution finished, let's start editing text below the error.
await u.codeLocator.click()
@ -236,13 +236,9 @@ const sketch001 = startSketchAt([-0, -0])
page,
}) => {
const u = await getUtils(page)
await page.addInitScript(
async ({ code }) => {
localStorage.setItem('persistCode', code)
;(window as any).playwrightSkipFilePicker = true
},
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
)
await page.addInitScript(async (code) => {
localStorage.setItem('persistCode', code)
}, TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR)
await page.setViewportSize({ width: 1000, height: 500 })
@ -329,7 +325,7 @@ const sketch001 = startSketchAt([-0, -0])
await expect(exportingToastMessage).toBeVisible()
// Expect it to succeed.
await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
await expect(exportingToastMessage).not.toBeVisible()
await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible()
@ -341,7 +337,6 @@ const sketch001 = startSketchAt([-0, -0])
}) => {
// This is being weird on ubuntu and windows.
test.skip(
// eslint-disable-next-line jest/valid-title
process.platform === 'linux' || process.platform === 'win32',
'This test is being weird on ubuntu'
)
@ -425,10 +420,6 @@ const sketch001 = startSketchAt([-0, -0])
`Network health indicator only appears in modeling view`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
@ -481,7 +472,7 @@ async function clickExportButton(page: Page) {
// Click the export button
await exportButton.click()
// Click the gltf.
// Click the stl.
const gltfOption = page.getByRole('option', { name: 'glTF' })
await expect(gltfOption).toBeVisible()

View File

@ -344,108 +344,111 @@ test.describe('Sketch tests', () => {
})
})
test('Can edit a sketch that has been extruded in the same pipe', async ({
page,
}) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -10.01], %)
// failing for the same reason as "Can undo a sketch modification with ctrl+z"
// please fix together
test.fixme(
'Can edit a sketch that has been extruded in the same pipe',
async ({ page }) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -0.38], %)
|> tangentialArcTo([24.95, -5.38], %)
|> close(%)
|> extrude(5, %)`
)
})
)
})
await page.setViewportSize({ width: 1200, height: 500 })
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await u.waitForAuthSkipAppStart()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.waitForTimeout(100)
await u.openAndClearDebugPanel()
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
vantage: { x: 0, y: -1250, z: 580 },
center: { x: 0, y: 0, z: 0 },
up: { x: 0, y: 0, z: 1 },
},
})
await page.waitForTimeout(100)
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await page.waitForTimeout(100)
await page.waitForTimeout(100)
await u.openAndClearDebugPanel()
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
vantage: { x: 0, y: -1250, z: 580 },
center: { x: 0, y: 0, z: 0 },
up: { x: 0, y: 0, z: 1 },
},
})
await page.waitForTimeout(100)
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await page.waitForTimeout(100)
const startPX = [665, 397]
const startPX = [665, 458]
const dragPX = 40
const dragPX = 40
await page.getByText('startProfileAt([4.61, -10.01], %)').click()
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
let prevContent = await page.locator('.cm-content').innerText()
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
let prevContent = await page.locator('.cm-content').innerText()
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
// drag startProfieAt handle
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: startPX[0], y: startPX[1] },
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
})
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// drag startProfieAt handle
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: startPX[0], y: startPX[1] },
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
})
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// drag line handle
await page.waitForTimeout(100)
// drag line handle
await page.waitForTimeout(100)
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
await page.waitForTimeout(100)
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
})
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
await page.waitForTimeout(100)
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
})
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// drag tangentialArcTo handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
targetPosition: {
x: tangentEnd.x + dragPX,
y: tangentEnd.y + dragPX,
},
})
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// drag tangentialArcTo handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 },
targetPosition: {
x: tangentEnd.x + dragPX,
y: tangentEnd.y + dragPX,
},
})
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.12, -12.68], %)
|> line([15.39, -2.78], %)
|> tangentialArcTo([27.6, -3.05], %)
|> close(%)
|> extrude(5, %)
`)
})
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.12, -16.82], %)
|> line([15.4, -2.74], %)
|> tangentialArcTo([24.95, -5.38], %)
|> line([2.65, -2.69], %)
|> close(%)
|> extrude(5, %)`)
}
)
test('Can edit a sketch that has been revolved in the same pipe', async ({
page,
@ -599,7 +602,7 @@ test.describe('Sketch tests', () => {
await expect(u.codeLocator).toHaveText(codeStr)
// exit the sketch, reset relative clicker
await click00r(undefined, undefined)
click00r(undefined, undefined)
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')

View File

@ -53,7 +53,6 @@ test(
async ({ page, context }) => {
// skip on macos and windows.
test.skip(
// eslint-disable-next-line jest/valid-title
process.platform === 'darwin' || process.platform === 'win32',
'Skip on macos and windows'
)
@ -964,69 +963,3 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
})
})
})
test('theme persists', async ({ page, context }) => {
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)
|> extrude(10, %)
`
)
}, KCL_DEFAULT_LENGTH)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await page.waitForTimeout(500)
// await page.getByRole('link', { name: 'Settings Settings (tooltip)' }).click()
await expect(page.getByTestId('settings-link')).toBeVisible()
await page.getByTestId('settings-link').click()
// open user settingns
await page.getByRole('radio', { name: 'person User' }).click()
await page.getByTestId('app-theme').selectOption('light')
await page.getByTestId('settings-close-button').click()
const networkToggle = page.getByTestId('network-toggle')
// simulate network down
await u.emulateNetworkConditions({
offline: true,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
// Disconnect and reconnect to check the theme persists through a reload
// Expect the network to be down
await expect(networkToggle).toContainText('Offline')
// simulate network up
await u.emulateNetworkConditions({
offline: false,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
await expect(networkToggle).toContainText('Connected')
await expect(page.getByText('building scene')).not.toBeVisible()
await expect(page, 'expect screenshot to have light theme').toHaveScreenshot({
maxDiffPixels: 100,
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -5,7 +5,6 @@ import {
TestInfo,
BrowserContext,
_electron as electron,
Locator,
} from '@playwright/test'
import { EngineCommand } from 'lang/std/artifactGraph'
import os from 'os'
@ -95,8 +94,6 @@ async function expectCmdLog(page: Page, locatorStr: string, timeout = 5000) {
await expect(page.locator(locatorStr).last()).toBeVisible({ timeout })
}
// Ignoring the lint since I assume someone will want to use this for a test.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function waitForDefaultPlanesToBeVisible(page: Page) {
await page.waitForFunction(
() =>
@ -105,21 +102,17 @@ async function waitForDefaultPlanesToBeVisible(page: Page) {
)
}
async function openPane(page: Page, testId: string) {
const locator = page.getByTestId(testId)
await expect(locator).toBeVisible()
const isOpen = (await locator?.getAttribute('aria-pressed')) === 'true'
async function openKclCodePanel(page: Page) {
const paneLocator = page.getByTestId('code-pane-button')
const ariaSelected = await paneLocator?.getAttribute('aria-pressed')
const isOpen = ariaSelected === 'true'
if (!isOpen) {
await locator.click()
await expect(locator).toHaveAttribute('aria-pressed', 'true')
await paneLocator.click()
await expect(paneLocator).toHaveAttribute('aria-pressed', 'true')
}
}
async function openKclCodePanel(page: Page) {
await openPane(page, 'code-pane-button')
}
async function closeKclCodePanel(page: Page) {
const paneLocator = page.getByTestId('code-pane-button')
const ariaSelected = await paneLocator?.getAttribute('aria-pressed')
@ -132,7 +125,14 @@ async function closeKclCodePanel(page: Page) {
}
async function openDebugPanel(page: Page) {
await openPane(page, 'debug-pane-button')
const debugLocator = page.getByTestId('debug-pane-button')
await expect(debugLocator).toBeVisible()
const isOpen = (await debugLocator?.getAttribute('aria-pressed')) === 'true'
if (!isOpen) {
await debugLocator.click()
await expect(debugLocator).toHaveAttribute('aria-pressed', 'true')
}
}
async function closeDebugPanel(page: Page) {
@ -145,28 +145,6 @@ async function closeDebugPanel(page: Page) {
}
}
async function openFilePanel(page: Page) {
await openPane(page, 'files-pane-button')
}
async function closeFilePanel(page: Page) {
const fileLocator = page.getByTestId('files-pane-button')
await expect(fileLocator).toBeVisible()
const isOpen = (await fileLocator?.getAttribute('aria-pressed')) === 'true'
if (isOpen) {
await fileLocator.click()
await expect(fileLocator).not.toHaveAttribute('aria-pressed', 'true')
}
}
async function openVariablesPane(page: Page) {
await openPane(page, 'variables-pane-button')
}
async function openLogsPane(page: Page) {
await openPane(page, 'logs-pane-button')
}
async function waitForCmdReceive(page: Page, commandType: string) {
return page
.locator(`[data-receive-command-type="${commandType}"]`)
@ -193,8 +171,7 @@ export const wiggleMove = async (
const isElVis = await page.locator(locator).isVisible()
if (isElVis) return
}
// x1 is 0.
const y1 = Math.sin((tau / steps) * j * freq) * amplitude
const [x1, y1] = [0, Math.sin((tau / steps) * j * freq) * amplitude]
const [x2, y2] = [
Math.cos(-ang * deg) * i - Math.sin(-ang * deg) * y1,
Math.sin(-ang * deg) * i + Math.cos(-ang * deg) * y1,
@ -340,10 +317,6 @@ export async function getUtils(page: Page) {
closeKclCodePanel: () => closeKclCodePanel(page),
openDebugPanel: () => openDebugPanel(page),
closeDebugPanel: () => closeDebugPanel(page),
openFilePanel: () => openFilePanel(page),
closeFilePanel: () => closeFilePanel(page),
openVariablesPane: () => openVariablesPane(page),
openLogsPane: () => openLogsPane(page),
openAndClearDebugPanel: async () => {
await openDebugPanel(page)
return clearCommandLogs(page)
@ -479,10 +452,7 @@ export async function getUtils(page: Page) {
return page.evaluate('window.tearDown()')
}
return cdpSession?.send(
'Network.emulateNetworkConditions',
networkOptions
)
cdpSession?.send('Network.emulateNetworkConditions', networkOptions)
},
}
}
@ -569,34 +539,17 @@ export interface Paths {
export const doExport = async (
output: Models['OutputFormat_type'],
page: Page,
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
isElectron = false
): Promise<Paths> => {
if (exportFrom === 'dropdown') {
if (!isElectron) {
await page.getByRole('button', { name: APP_NAME }).click()
const exportMenuButton = page.getByRole('button', {
name: 'Export current part',
})
await expect(exportMenuButton).toBeVisible()
await exportMenuButton.click()
} else if (exportFrom === 'sidebarButton') {
await expect(page.getByTestId('export-pane-button')).toBeVisible()
} else {
await page.getByTestId('export-pane-button').click()
} else if (exportFrom === 'commandBar') {
const commandBarButton = page.getByRole('button', { name: 'Commands' })
await expect(commandBarButton).toBeVisible()
// Click the command bar button
await commandBarButton.click()
// Wait for the command bar to appear
const cmdSearchBar = page.getByPlaceholder('Search commands')
await expect(cmdSearchBar).toBeVisible()
const textToCadCommand = page.getByRole('option', {
name: 'floppy disk arrow Export',
})
await expect(textToCadCommand.first()).toBeVisible()
// Click the Text-to-CAD command
await textToCadCommand.first().click()
}
await expect(page.getByTestId('command-bar')).toBeVisible()
@ -624,7 +577,7 @@ export const doExport = async (
const [downloadPromise1, downloadResolve1] = getPromiseAndResolve()
let downloadCnt = 0
if (exportFrom === 'dropdown')
if (!isElectron)
page.on('download', async (download) => {
if (downloadCnt === 0) {
downloadResolve1(download)
@ -632,7 +585,7 @@ export const doExport = async (
downloadCnt++
})
await page.getByRole('button', { name: 'Submit command' }).click()
if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') {
if (isElectron) {
return {
modelPath: '',
imagePath: '',
@ -696,7 +649,6 @@ export async function tearDown(page: Page, testInfo: TestInfo) {
export async function setup(context: BrowserContext, page: Page) {
await context.addInitScript(
async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY }) => {
localStorage.clear()
localStorage.setItem('TOKEN_PERSIST_KEY', token)
localStorage.setItem('persistCode', ``)
localStorage.setItem(settingsKey, settings)
@ -732,8 +684,6 @@ export async function setup(context: BrowserContext, page: Page) {
])
// kill animations, speeds up tests and reduced flakiness
await page.emulateMedia({ reducedMotion: 'reduce' })
await page.reload()
}
export async function setupElectron({
@ -795,22 +745,3 @@ export async function setupElectron({
return { electronApp, page }
}
export async function isOutOfViewInScrollContainer(
element: Locator,
container: Locator
): Promise<boolean> {
const elementBox = await element.boundingBox({ timeout: 5_000 })
const containerBox = await container.boundingBox({ timeout: 5_000 })
let isOutOfView = false
if (elementBox && containerBox)
return (
elementBox.y + elementBox.height > containerBox.y + containerBox.height ||
elementBox.y < containerBox.y ||
elementBox.x + elementBox.width > containerBox.x + containerBox.width ||
elementBox.x < containerBox.x
)
return isOutOfView
}

View File

@ -72,7 +72,7 @@ test.describe('Testing constraints', () => {
page.getByRole('button', { name: 'Exit Sketch' })
).not.toBeVisible()
})
test(`Remove constraints`, async ({ page }) => {
test(`Test remove constraints`, async ({ page }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',

View File

@ -977,6 +977,10 @@ const part001 = startSketchOn('XZ')
const hoverPos = { x: segmentToDelete.x, y: segmentToDelete.y }
await page.mouse.move(0, 0)
await page.waitForTimeout(1000)
let x = 0,
y = 0
x = hoverPos.x + Math.cos(ang * deg) * 32
y = hoverPos.y - Math.sin(ang * deg) * 32
await page.mouse.move(hoverPos.x, hoverPos.y)
await wiggleMove(
page,

View File

@ -4,6 +4,7 @@ import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { TEST_SETTINGS_KEY, TEST_SETTINGS_CORRUPTED } from './storageStates'
import * as TOML from '@iarna/toml'
import { APP_NAME } from 'lib/constants'
test.beforeEach(async ({ context, page }) => {
await setup(context, page)
@ -115,7 +116,8 @@ test.describe('Testing settings', () => {
).not.toBeChecked()
})
test('Project and user settings can be reset', async ({ page }) => {
// TODO fixme reset doesn't seem to work for color setting
test.fixme('Project and user settings can be reset', async ({ page }) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
@ -160,11 +162,6 @@ test.describe('Testing settings', () => {
// Click the reset settings button.
await resetButton.click()
await expect(page.getByText('Settings restored to default')).toBeVisible()
await expect(
page.getByText('Settings restored to default')
).not.toBeVisible()
// Verify it is now set to the inherited user value
await expect(themeColorSetting).toHaveValue(settingValues.default)
@ -196,10 +193,6 @@ test.describe('Testing settings', () => {
`Project settings override user settings on desktop`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
test.skip(
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
@ -212,6 +205,7 @@ test.describe('Testing settings', () => {
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)

View File

@ -1,5 +1,7 @@
import { test, expect, Page } from '@playwright/test'
import { getUtils, setup, tearDown } from './test-utils'
import * as fsp from 'fs/promises'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import { join } from 'path'
test.beforeEach(async ({ context, page }) => {
await setup(context, page)
@ -190,8 +192,7 @@ test.describe('Text-to-CAD tests', () => {
await expect(prompt.first()).toBeVisible()
// Type the prompt.
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
await page.keyboard.type(randomPrompt)
await page.keyboard.type('akjsndladf ghgsssswefiuwq22262664')
await page.waitForTimeout(1000)
await page.keyboard.press('Enter')

View File

@ -1,34 +0,0 @@
import { test, expect } from '@playwright/test'
import { setupElectron, tearDown } from './test-utils'
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test.describe('Electron user sidebar menu tests', () => {
test(
'User settings has correct shortcut',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async () => {},
})
await page.setViewportSize({ width: 1200, height: 500 })
// Open the user sidebar menu.
await page.getByTestId('user-sidebar-toggle').click()
// No space after "User settings" since it's textContent.
const text =
process.platform === 'darwin' ? 'User settings⌘,' : 'User settingsCtrl,'
const userSettingsButton = page.getByTestId('user-settings')
await expect(userSettingsButton).toBeVisible()
await expect(userSettingsButton).toHaveText(text)
await electronApp.close()
}
)
})

View File

@ -631,7 +631,6 @@
"errorno": {
"description": "The error number.",
"format": "int64",
"nullable": true,
"type": "integer"
},
"reason": {
@ -662,7 +661,6 @@
"tar_temp": {
"description": "The target temperature.",
"format": "int64",
"nullable": true,
"type": "integer"
},
"target": {
@ -673,8 +671,10 @@
},
"required": [
"command",
"errorno",
"result",
"sequence_id",
"tar_temp",
"target"
],
"type": "object"
@ -1155,59 +1155,6 @@
],
"type": "object"
},
{
"additionalProperties": true,
"description": "A gcode file.",
"properties": {
"command": {
"enum": [
"gcode_file"
],
"type": "string"
},
"param": {
"description": "The param.",
"nullable": true,
"type": "string"
},
"print_type": {
"description": "The print type.",
"nullable": true,
"type": "string"
},
"reason": {
"allOf": [
{
"$ref": "#/components/schemas/Reason"
}
],
"description": "The reason for the message."
},
"result": {
"allOf": [
{
"$ref": "#/components/schemas/Result"
}
],
"description": "The result of the command."
},
"sequence_id": {
"allOf": [
{
"$ref": "#/components/schemas/SequenceId"
}
],
"description": "The sequence id."
}
},
"required": [
"command",
"reason",
"result",
"sequence_id"
],
"type": "object"
},
{
"additionalProperties": true,
"description": "Project file.",

View File

@ -19,7 +19,7 @@
"@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@csstools/postcss-oklab-function": "^4.0.2",
"@csstools/postcss-oklab-function": "^3.0.16",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
@ -53,7 +53,7 @@
"react-json-view": "^1.21.3",
"react-modal": "^3.16.1",
"react-modal-promise": "^1.0.2",
"react-router-dom": "^6.26.1",
"react-router-dom": "^6.23.1",
"sketch-helpers": "^0.0.4",
"three": "^0.166.1",
"ua-parser-js": "^1.0.37",
@ -81,23 +81,22 @@
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh",
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
"build:wasm": "(cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm": "(cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
"lint": "eslint --fix src e2e",
"lint": "eslint --fix src",
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
"postinstall": "yarn xstate:typegen",
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
"make:dev": "make dev",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"tron:start": "electron-forge start",
"tron:package": "electron-forge package",
"tron:make": "electron-forge make",
"tron:publish": "electron-forge publish",
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron"
"electron:start": "electron-forge start",
"electron:package": "electron-forge package",
"electron:make": "electron-forge make",
"electron:publish": "electron-forge publish",
"electron:e2e:local": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron"
},
"prettier": {
"trailingComma": "es5",
@ -131,17 +130,17 @@
"@electron/fuses": "^1.8.0",
"@iarna/toml": "^2.2.5",
"@lezer/generator": "^1.7.1",
"@playwright/test": "^1.46.1",
"@playwright/test": "^1.45.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^15.0.2",
"@types/d3-force": "^3.0.10",
"@types/electron": "^1.6.10",
"@types/isomorphic-fetch": "^0.0.39",
"@types/mocha": "^10.0.6",
"@types/node": "^22.4.2",
"@types/node": "^18.19.31",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4",
"@types/react": "^18.3.4",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.2.25",
"@types/react-modal": "^3.16.3",
"@types/three": "^0.163.0",
@ -157,7 +156,7 @@
"@xstate/cli": "^0.5.17",
"autoprefixer": "^10.4.19",
"d3-force": "^3.0.0",
"electron": "^32.0.1",
"electron": "^31.2.1",
"eslint": "^8.0.1",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-css-modules": "^2.12.0",

View File

@ -185,8 +185,6 @@ export function Toolbar({
maybeIconConfig[0].disabled
}
name={maybeIconConfig[0].title}
// aria-description is still in ARIA 1.3 draft.
// eslint-disable-next-line jsx-a11y/aria-props
aria-description={maybeIconConfig[0].description}
onClick={() =>
maybeIconConfig[0].onClick(configCallbackProps)
@ -227,8 +225,6 @@ export function Toolbar({
(!itemConfig.showTitle ? ' !px-0' : '')
}
name={itemConfig.title}
// aria-description is still in ARIA 1.3 draft.
// eslint-disable-next-line jsx-a11y/aria-props
aria-description={itemConfig.description}
aria-pressed={itemConfig.isActive}
disabled={

View File

@ -52,21 +52,24 @@ const DownloadAppBanner = () => {
</a>{' '}
to download the app for the best experience.
</p>
{!navigator?.userAgent.includes('Chrome') && (
<p className="mt-6">
If you want to stay here on the web-app, we currently only support
Chrome. Please use{' '}
<a
href="https://www.google.com/chrome/"
rel="noopener noreferrer"
target="_blank"
className="!text-warn-80 dark:!text-warn-80 dark:hover:!text-warn-70 underline"
>
this link
</a>{' '}
to download it.
</p>
)}
<p className="mt-6">
If you're on Linux and the browser is your only way to use the app,
you can permanently dismiss this banner by{' '}
<a
onClick={() => {
setIsBannerDismissed(true)
settings.send({
type: 'set.app.dismissWebBanner',
data: { level: 'user', value: true },
})
}}
href="/"
className="!text-warn-80 dark:!text-warn-80 dark:hover:!text-warn-70 underline"
>
toggling the App &gt; Dismiss Web Banner setting
</a>
.
</p>
</div>
</Dialog.Panel>
</Dialog>

View File

@ -153,34 +153,33 @@ export const FileMachineProvider = ({
event: EventFrom<typeof fileMachine, 'Rename file'>
) => {
const { oldName, newName, isDir } = event.data
const name = newName
? newName.endsWith(FILE_EXT) || isDir
? newName
: newName + FILE_EXT
: DEFAULT_FILE_NAME
const name = newName ? newName : DEFAULT_FILE_NAME
const oldPath = window.electron.path.join(
context.selectedDirectory.path,
oldName
)
const newPath = window.electron.path.join(
const newDirPath = window.electron.path.join(
context.selectedDirectory.path,
name
)
const newPath =
newDirPath + (name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT)
window.electron.rename(oldPath, newPath)
await window.electron.rename(oldPath, newPath)
if (!file) {
return Promise.reject(new Error('file is not defined'))
}
if (oldPath === file.path && project?.path) {
const currentFilePath = window.electron.path.join(file.path, file.name)
if (oldPath === currentFilePath && project?.path) {
// If we just renamed the current file, navigate to the new path
navigate(`..${PATHS.FILE}/${encodeURIComponent(newPath)}`)
} else if (file?.path.includes(oldPath)) {
// If we just renamed a directory that the current file is in, navigate to the new path
navigate(
`..${PATHS.FILE}/${encodeURIComponent(
file.path.replace(oldPath, newPath)
file.path.replace(oldPath, newDirPath)
)}`
)
}

View File

@ -464,10 +464,7 @@ export const FileTreeInner = ({
}, [documentHasFocus])
return (
<div
className="overflow-auto pb-12 absolute inset-0"
data-testid="file-pane-scroll-container"
>
<div className="overflow-auto pb-12 absolute inset-0">
<ul
className="m-0 p-0 text-sm"
onClickCapture={(e) => {

View File

@ -23,10 +23,7 @@ export function HelpMenu(props: React.PropsWithChildren) {
return (
<Popover className="relative">
<Popover.Button
className="grid p-0 m-0 border-none rounded-full place-content-center"
data-testid="help-button"
>
<Popover.Button className="grid p-0 m-0 border-none rounded-full place-content-center">
<CustomIcon
name="questionMark"
className="rounded-full w-7 h-7 bg-chalkboard-110 dark:bg-chalkboard-80 text-chalkboard-10"
@ -98,7 +95,6 @@ export function HelpMenu(props: React.PropsWithChildren) {
: PATHS.HOME + PATHS.SETTINGS_KEYBINDINGS
navigate(targetPath)
}}
data-testid="keybindings-button"
>
Keyboard shortcuts
</HelpMenuItem>

View File

@ -11,6 +11,7 @@ import toast from 'react-hot-toast'
import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow'
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
export function LowerRightControls({
children,
@ -24,6 +25,8 @@ export function LowerRightControls({
const linkOverrideClassName =
'!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30'
const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'
async function reportbug(event: {
preventDefault: () => void
stopPropagation: () => void
@ -71,7 +74,7 @@ export function LowerRightControls({
rel="noopener noreferrer"
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}
>
v{APP_VERSION}
v{isTestEnv ? '11.22.33' : APP_VERSION}
</a>
<a
onClick={reportbug}
@ -93,7 +96,6 @@ export function LowerRightControls({
? filePath + PATHS.SETTINGS + '?tab=project'
: PATHS.HOME + PATHS.SETTINGS
}
data-testid="settings-link"
>
<CustomIcon
name="settings"

View File

@ -63,10 +63,8 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
data: { name: 'Make', groupId: 'modeling' },
})
},
hide: () => !isDesktop(),
disable: () => {
return machineManager.noMachinesReason()
},
hide: () => machineManager.machineCount() === 0,
hideOnPlatform: 'web',
},
]
const filteredActions: SidebarAction[] = sidebarActions.filter(
@ -188,7 +186,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
iconSize: 'md',
}}
onClick={action.action}
disabledText={action.disable?.()}
/>
))}
</ul>
@ -241,7 +238,6 @@ interface ModelingPaneButtonProps
onClick: () => void
paneIsOpen?: boolean
showBadge?: BadgeInfoComputed
disabledText?: string
}
function ModelingPaneButton({
@ -249,7 +245,6 @@ function ModelingPaneButton({
onClick,
paneIsOpen,
showBadge,
disabledText,
...props
}: ModelingPaneButtonProps) {
useHotkeys(paneConfig.keybinding, onClick, {
@ -263,8 +258,6 @@ function ModelingPaneButton({
onClick={onClick}
name={paneConfig.title}
data-testid={paneConfig.id + '-pane-button'}
disabled={disabledText !== undefined}
aria-disabled={disabledText !== undefined}
{...props}
>
<ActionIcon
@ -291,7 +284,6 @@ function ModelingPaneButton({
>
<span className="flex-1">
{paneConfig.title}
{disabledText !== undefined ? ` (${disabledText})` : ''}
{paneIsOpen !== undefined ? ` pane` : ''}
</span>
<kbd className="hotkey text-xs capitalize">
@ -334,5 +326,4 @@ export type SidebarAction = {
action: () => void
hideOnPlatform?: 'desktop' | 'web'
hide?: boolean | (() => boolean)
disable?: () => string | undefined
}

View File

@ -9,9 +9,7 @@ export const NetworkMachineIndicator = ({
}: {
className?: string
}) => {
const machineCount = machineManager.machineCount()
const reason = machineManager.noMachinesReason()
const machineCount = Object.keys(machineManager.machines).length
return isDesktop() ? (
<Popover className="relative">
<Popover.Button
@ -28,7 +26,7 @@ export const NetworkMachineIndicator = ({
</p>
)}
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
Network machines ({machineCount}) {reason && `: ${reason}`}
Network machines ({machineCount})
</Tooltip>
</Popover.Button>
<Popover.Panel

View File

@ -7,29 +7,12 @@ export default function usePlatform() {
const [platformName, setPlatformName] = useState<Platform>('')
useEffect(() => {
function getPlatform(): Platform {
const platform = window.electron.platform ?? ''
// https://nodejs.org/api/process.html#processplatform
switch (platform) {
case 'darwin':
return 'macos'
case 'win32':
return 'windows'
// We don't currently care to distinguish between these.
case 'android':
case 'freebsd':
case 'linux':
case 'openbsd':
case 'sunos':
return 'linux'
default:
console.error('Unknown platform:', platform)
return ''
}
async function getPlatform() {
setPlatformName((window.electron.platform ?? '') as Platform)
}
if (isDesktop()) {
setPlatformName(getPlatform())
void getPlatform()
} else {
if (navigator.userAgent.indexOf('Mac') !== -1) {
setPlatformName('macos')

View File

@ -2026,7 +2026,7 @@ describe('parsing errors', () => {
expect(result).toBeInstanceOf(KCLError)
const error = result as KCLError
expect(error.kind).toBe('syntax')
expect(error.msg).toBe('Unexpected token: (')
expect(error.msg).toBe('Unexpected token')
expect(error.sourceRanges).toEqual([[27, 28]])
})
})

View File

@ -563,7 +563,6 @@ export function createArrayExpression(
start: 0,
end: 0,
digest: null,
nonCodeMeta: { nonCodeNodes: {}, start: [], digest: null },
elements,
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

View File

@ -19,6 +19,11 @@ import init, {
parse_project_route,
base64_decode,
} from '../wasm-lib/pkg/wasm_lib'
import {
configurationToSettingsPayload,
projectConfigurationToSettingsPayload,
} from 'lib/settings/settingsUtils'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { EngineCommandManager } from './std/engineConnection'
@ -35,9 +40,6 @@ import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { TEST } from 'env'
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
import { err } from 'lib/trap'
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { DeepPartial } from 'lib/types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
@ -568,30 +570,31 @@ export function tomlStringify(toml: any): string | Error {
return toml_stringify(JSON.stringify(toml))
}
export function defaultAppSettings(): DeepPartial<Configuration> | Error {
return default_app_settings()
export function defaultAppSettings(): Partial<SaveSettingsPayload> {
// Immediately go from Configuration -> Partial<SaveSettingsPayload>
// The returned Rust type is Configuration but it's a lie. Every
// property in that returned object is optional. The Partial<T> essentially
// brings that type in-line with that definition.
return configurationToSettingsPayload(default_app_settings())
}
export function parseAppSettings(
toml: string
): DeepPartial<Configuration> | Error {
return parse_app_settings(toml)
export function parseAppSettings(toml: string): Partial<SaveSettingsPayload> {
const parsed = parse_app_settings(toml)
return configurationToSettingsPayload(parsed)
}
export function defaultProjectSettings():
| DeepPartial<ProjectConfiguration>
| Error {
return default_project_settings()
export function defaultProjectSettings(): Partial<SaveSettingsPayload> {
return projectConfigurationToSettingsPayload(default_project_settings())
}
export function parseProjectSettings(
toml: string
): DeepPartial<ProjectConfiguration> | Error {
return parse_project_settings(toml)
): Partial<SaveSettingsPayload> {
return projectConfigurationToSettingsPayload(parse_project_settings(toml))
}
export function parseProjectRoute(
configuration: DeepPartial<Configuration>,
configuration: Partial<SaveSettingsPayload>,
route_str: string
): ProjectRoute | Error {
return parse_project_route(JSON.stringify(configuration), route_str)

View File

@ -154,6 +154,10 @@ export function buildCommandArgument<
} satisfies Omit<CommandArgument<O, T>, 'inputType'>
if (arg.inputType === 'options') {
if (!(arg.options || arg.optionsFromContext)) {
throw new Error('Options must be provided for options input type')
}
return {
inputType: arg.inputType,
...baseCommandArgument,

View File

@ -3,9 +3,11 @@ import { Models } from '@kittycad/lib'
import { Project } from 'wasm-lib/kcl/bindings/Project'
import { ProjectState } from 'wasm-lib/kcl/bindings/ProjectState'
import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import {
defaultAppSettings,
tomlStringify,
parseAppSettings,
parseProjectSettings,
} from 'lang/wasm'
@ -16,9 +18,6 @@ import {
PROJECT_SETTINGS_FILE_NAME,
SETTINGS_FILE_NAME,
} from './constants'
import { DeepPartial } from './types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
export { parseProjectRoute } from 'lang/wasm'
export async function renameProjectDirectory(
@ -62,13 +61,10 @@ export async function renameProjectDirectory(
}
export async function ensureProjectDirectoryExists(
config: DeepPartial<Configuration>
config: Partial<SaveSettingsPayload>
): Promise<string | undefined> {
const projectDir =
config.settings?.app?.project_directory ||
config.settings?.project?.directory
const projectDir = config.app?.projectDirectory
if (!projectDir) {
console.error('projectDir is falsey', config)
return Promise.reject(new Error('projectDir is falsey'))
}
try {
@ -85,13 +81,12 @@ export async function ensureProjectDirectoryExists(
export async function createNewProjectDirectory(
projectName: string,
initialCode?: string,
configuration?: DeepPartial<Configuration> | Error
configuration?: Partial<SaveSettingsPayload>
): Promise<Project> {
if (!configuration) {
configuration = await readAppSettingsFile()
}
if (err(configuration)) return Promise.reject(configuration)
const mainDir = await ensureProjectDirectoryExists(configuration)
if (!projectName) {
@ -129,13 +124,11 @@ export async function createNewProjectDirectory(
}
export async function listProjects(
configuration?: DeepPartial<Configuration> | Error
configuration?: Partial<SaveSettingsPayload>
): Promise<Project[]> {
if (configuration === undefined) {
configuration = await readAppSettingsFile()
}
if (err(configuration)) return Promise.reject(configuration)
const projectDir = await ensureProjectDirectoryExists(configuration)
const projects = []
if (!projectDir) return Promise.reject(new Error('projectDir was falsey'))
@ -186,7 +179,7 @@ const collectAllFilesRecursiveFrom = async (path: string) => {
return Promise.reject(new Error(`Path ${path} is not a directory`))
}
const pathParts = path.split(window.electron.path.sep)
const pathParts = path.split('/')
let entry: FileEntry = {
name: pathParts.slice(-1)[0],
path,
@ -365,9 +358,10 @@ export async function getProjectInfo(projectPath: string): Promise<Project> {
// Write project settings file.
export async function writeProjectSettingsFile(
projectPath: string,
tomlStr: string
configuration: Partial<SaveSettingsPayload>
): Promise<void> {
const projectSettingsFilePath = await getProjectSettingsFilePath(projectPath)
const tomlStr = tomlStringify({ settings: configuration })
if (err(tomlStr)) return Promise.reject(tomlStr)
return window.electron.writeFile(projectSettingsFilePath, tomlStr)
}
@ -413,16 +407,13 @@ const getProjectSettingsFilePath = async (projectPath: string) => {
}
export const getInitialDefaultDir = async () => {
if (!window.electron) {
return ''
}
const dir = await window.electron.getPath('documents')
return window.electron.path.join(dir, PROJECT_FOLDER)
}
export const readProjectSettingsFile = async (
projectPath: string
): Promise<DeepPartial<ProjectConfiguration>> => {
): Promise<Partial<SaveSettingsPayload>> => {
let settingsPath = await getProjectSettingsFilePath(projectPath)
// Check if this file exists.
@ -437,9 +428,6 @@ export const readProjectSettingsFile = async (
const configToml = await window.electron.readFile(settingsPath)
const configObj = parseProjectSettings(configToml)
if (err(configObj)) {
return Promise.reject(configObj)
}
return configObj
}
@ -450,25 +438,23 @@ export const readAppSettingsFile = async () => {
} catch (e) {
if (e === 'ENOENT') {
const config = defaultAppSettings()
if (err(config)) return Promise.reject(config)
if (!config.settings?.app)
if (!config.app) {
return Promise.reject(new Error('config.app is falsey'))
config.settings.app.project_directory = await getInitialDefaultDir()
}
config.app.projectDirectory = await getInitialDefaultDir()
return config
}
}
const configToml = await window.electron.readFile(settingsPath)
const configObj = parseAppSettings(configToml)
if (err(configObj)) {
return Promise.reject(configObj)
}
return configObj
}
export const writeAppSettingsFile = async (tomlStr: string) => {
export const writeAppSettingsFile = async (
config: Partial<SaveSettingsPayload>
) => {
const appSettingsFilePath = await getAppSettingsFilePath()
const tomlStr = tomlStringify({ settings: config })
if (err(tomlStr)) return Promise.reject(tomlStr)
return window.electron.writeFile(appSettingsFilePath, tomlStr)
}

View File

@ -38,9 +38,6 @@ const bracket = startSketchOn('XY')
tags: [getPreviousAdjacentEdge(outerEdge)]
}, %)`
/**
* @throws Error if the search text is not found in the example code.
*/
function findLineInExampleCode({
searchText,
example = bracket,
@ -51,8 +48,6 @@ function findLineInExampleCode({
const lines = example.split('\n')
const lineNumber = lines.findIndex((l) => l.includes(searchText)) + 1
if (lineNumber === 0) {
// We are exporting a constant, so we don't want to return an Error.
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
throw new Error(
`Could not find the line with search text "${searchText}" in the example code. Was it removed?`
)

View File

@ -65,13 +65,6 @@ export async function exportMake(data: ArrayBuffer): Promise<Response | null> {
console.log('response', response)
if (!response.ok) {
console.error('Error exporting', response)
const text = await response.text()
toast.error('Error exporting: ' + response.statusText + ' ' + text)
return null
}
return response
} catch (error) {
console.error('Error exporting', error)

View File

@ -271,7 +271,7 @@ export interface components {
* Format: int64
* @description The error number.
*/
errorno?: number | null
errorno: number
/** @description The reason for the message. */
reason?: components['schemas']['Reason'] | null
/** @description The result of the command. */
@ -282,7 +282,7 @@ export interface components {
* Format: int64
* @description The target temperature.
*/
tar_temp?: number | null
tar_temp: number
/**
* Format: int64
* @description The target.
@ -523,22 +523,6 @@ export interface components {
} & {
[key: string]: unknown
})
| ({
/** @enum {string} */
command: 'gcode_file'
/** @description The param. */
param?: string | null
/** @description The print type. */
print_type?: string | null
/** @description The reason for the message. */
reason: components['schemas']['Reason']
/** @description The result of the command. */
result: components['schemas']['Result']
/** @description The sequence id. */
sequence_id: components['schemas']['SequenceId']
} & {
[key: string]: unknown
})
| ({
/** @enum {string} */
command: 'project_file'

View File

@ -51,19 +51,6 @@ export class MachineManager {
return this._machineApiIp
}
// Get the reason message for why there are no machines.
noMachinesReason(): string | undefined {
if (this.machineCount() > 0) {
return undefined
}
if (this.machineApiIp === null) {
return 'Machine API server was not discovered'
}
return 'Machine API server was discovered, but no machines are available'
}
get currentMachine(): components['schemas']['Machine'] | null {
return this._currentMachine
}

View File

@ -4,10 +4,9 @@ import { isDesktop } from './isDesktop'
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
import { parseProjectRoute, readAppSettingsFile } from './desktop'
import { readLocalStorageAppSettingsFile } from './settings/settingsUtils'
import { SaveSettingsPayload } from './settings/settingsTypes'
import { err } from 'lib/trap'
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
import { DeepPartial } from './types'
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
const prependRoutes =
(routesObject: Record<string, string>) => (prepend: string) => {
@ -40,7 +39,7 @@ export const BROWSER_PATH = `%2F${BROWSER_PROJECT_NAME}%2F${BROWSER_FILE_NAME}${
export async function getProjectMetaByRouteId(
id?: string,
configuration?: DeepPartial<Configuration> | Error
configuration?: Partial<SaveSettingsPayload> | Error
): Promise<ProjectRoute | undefined> {
if (!id) return undefined

View File

@ -10,7 +10,7 @@ import {
} from 'lib/constants'
import { loadAndValidateSettings } from './settings/settingsUtils'
import makeUrlPathRelative from './makeUrlPathRelative'
import { codeManager } from 'lib/singletons'
import { codeManager, kclManager } from 'lib/singletons'
import { fileSystemManager } from 'lang/std/fileSystemManager'
import {
getProjectInfo,
@ -107,6 +107,8 @@ export const fileLoader: LoaderFunction = async (
// the file system and not the editor.
codeManager.updateCurrentFilePath(current_file_path)
codeManager.updateCodeStateEditor(code)
// We don't want to call await on execute code since we don't want to block the UI
kclManager.executeCode(true)
}
// Set the file system manager to the project path
@ -123,22 +125,14 @@ export const fileLoader: LoaderFunction = async (
default_file: project_path,
}
const maybeProjectInfo = isDesktop()
? await getProjectInfo(project_path)
: null
console.log('maybeProjectInfo', {
maybeProjectInfo,
defaultProjectData,
projectPathData,
})
const projectData: IndexLoaderData = {
code,
project: maybeProjectInfo ?? defaultProjectData,
project: isDesktop()
? (await getProjectInfo(project_path)) ?? defaultProjectData
: defaultProjectData,
file: {
name: current_file_name || '',
path: current_file_path || '',
path: current_file_path?.split('/').slice(0, -1).join('/') ?? '',
children: [],
},
}

View File

@ -21,7 +21,6 @@ import {
} from 'lib/desktop'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
import { BROWSER_PROJECT_NAME } from 'lib/constants'
import { DeepPartial } from 'lib/types'
/**
* Convert from a rust settings struct into the JS settings struct.
@ -29,8 +28,8 @@ import { DeepPartial } from 'lib/types'
* for hiding and showing settings.
**/
export function configurationToSettingsPayload(
configuration: DeepPartial<Configuration>
): DeepPartial<SaveSettingsPayload> {
configuration: Configuration
): Partial<SaveSettingsPayload> {
return {
app: {
theme: appThemeToTheme(configuration?.settings?.app?.appearance?.theme),
@ -67,8 +66,8 @@ export function configurationToSettingsPayload(
}
export function projectConfigurationToSettingsPayload(
configuration: DeepPartial<ProjectConfiguration>
): DeepPartial<SaveSettingsPayload> {
configuration: ProjectConfiguration
): Partial<SaveSettingsPayload> {
return {
app: {
theme: appThemeToTheme(configuration?.settings?.app?.appearance?.theme),
@ -107,7 +106,7 @@ function localStorageProjectSettingsPath() {
}
export function readLocalStorageAppSettingsFile():
| DeepPartial<Configuration>
| Partial<SaveSettingsPayload>
| Error {
// TODO: Remove backwards compatibility after a few releases.
let stored =
@ -133,7 +132,7 @@ export function readLocalStorageAppSettingsFile():
}
function readLocalStorageProjectSettingsFile():
| DeepPartial<ProjectConfiguration>
| Partial<SaveSettingsPayload>
| Error {
// TODO: Remove backwards compatibility after a few releases.
let stored = localStorage.getItem(localStorageProjectSettingsPath()) ?? ''
@ -157,7 +156,7 @@ function readLocalStorageProjectSettingsFile():
export interface AppSettings {
settings: ReturnType<typeof createSettings>
configuration: DeepPartial<Configuration>
configuration: Partial<SaveSettingsPayload>
}
export async function loadAndValidateSettings(
@ -176,11 +175,7 @@ export async function loadAndValidateSettings(
if (err(appSettingsPayload)) return Promise.reject(appSettingsPayload)
const settings = createSettings()
setSettingsAtLevel(
settings,
'user',
configurationToSettingsPayload(appSettingsPayload)
)
setSettingsAtLevel(settings, 'user', appSettingsPayload)
// Load the project settings if they exist
if (projectPath) {
@ -192,18 +187,11 @@ export async function loadAndValidateSettings(
return Promise.reject(new Error('Invalid project settings'))
const projectSettingsPayload = projectSettings
setSettingsAtLevel(
settings,
'project',
projectConfigurationToSettingsPayload(projectSettingsPayload)
)
setSettingsAtLevel(settings, 'project', projectSettingsPayload)
}
// Return the settings object
return {
settings,
configuration: appSettingsPayload,
}
return { settings, configuration: appSettingsPayload }
}
export async function saveSettings(
@ -216,14 +204,21 @@ export async function saveSettings(
// Get the user settings.
const jsAppSettings = getChangedSettingsAtLevel(allSettings, 'user')
const appTomlString = tomlStringify({ settings: jsAppSettings })
if (err(appTomlString)) return
const tomlString = tomlStringify({ settings: jsAppSettings })
if (err(tomlString)) return
// Parse this as a Configuration.
const appSettings = parseAppSettings(tomlString)
if (err(appSettings)) return
const tomlString2 = tomlStringify({ settings: appSettings })
if (err(tomlString2)) return
// Write the app settings.
if (onDesktop) {
await writeAppSettingsFile(appTomlString)
await writeAppSettingsFile(appSettings)
} else {
localStorage.setItem(localStorageAppSettingsPath(), appTomlString)
localStorage.setItem(localStorageAppSettingsPath(), tomlString2)
}
if (!projectPath) {
@ -236,11 +231,19 @@ export async function saveSettings(
const projectTomlString = tomlStringify({ settings: jsProjectSettings })
if (err(projectTomlString)) return
// Parse this as a Configuration.
const projectSettings = parseProjectSettings(projectTomlString)
if (err(projectSettings)) return
const tomlStr = tomlStringify(projectSettings)
if (err(tomlStr)) return
// Write the project settings.
if (onDesktop) {
await writeProjectSettingsFile(projectPath, projectTomlString)
await writeProjectSettingsFile(projectPath, projectSettings)
} else {
localStorage.setItem(localStorageProjectSettingsPath(), projectTomlString)
localStorage.setItem(localStorageProjectSettingsPath(), tomlStr)
}
}

View File

@ -95,9 +95,3 @@ export function isEnumMember<T extends Record<string, unknown>>(
) {
return Object.values(e).includes(v)
}
// utility type to make all *nested* object properties optional
// https://www.geodev.me/blog/deeppartial-in-typescript
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}

View File

@ -131,6 +131,8 @@ ipcMain.handle('kittycad', (event, data) => {
)(data.args)
})
const SERVICE_NAME = '_machine-api._tcp.local.'
ipcMain.handle('find_machine_api', () => {
const timeoutAfterMs = 5000
return new Promise((resolve, reject) => {
@ -142,19 +144,8 @@ ipcMain.handle('find_machine_api', () => {
resolve(null)
})
console.log('Looking for machine API...')
bonjourEt.find(
{ protocol: 'tcp', type: 'machine-api' },
(service: Service) => {
console.log('Found machine API!', JSON.stringify(service))
if (!service.addresses || service.addresses?.length === 0) {
console.log('No addresses found for machine API!')
return resolve(null)
}
const ip = service.addresses[0]
const port = service.port
// We want to return the ip address of the machine API.
resolve(`${ip}:${port}`)
}
)
bonjourEt.find({ type: SERVICE_NAME }, (service: Service) => {
resolve(service.fqdn)
})
})
})

View File

@ -145,7 +145,7 @@ function OnboardingIntroductionInner() {
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
<h1 className="flex flex-wrap items-center gap-4 text-3xl font-bold">
<img
src={`${isDesktop() ? '.' : ''}/zma-logomark${getLogoTheme()}.svg`}
src={`./zma-logomark${getLogoTheme()}.svg`}
alt={APP_NAME}
className="h-20 max-w-full"
/>

View File

@ -13,18 +13,11 @@ import { AllSettingsFields } from 'components/Settings/AllSettingsFields'
import { AllKeybindingsFields } from 'components/Settings/AllKeybindingsFields'
import { KeybindingsSectionsList } from 'components/Settings/KeybindingsSectionsList'
import { isDesktop } from 'lib/isDesktop'
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
import { NODE_ENV } from 'env'
const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'
export const APP_VERSION =
isTestEnv && NODE_ENV === 'development'
? '11.22.33'
: isDesktop()
? // @ts-ignore
window.electron.packageJson.version
: 'main'
export const APP_VERSION = isDesktop()
? // @ts-ignore
window.electron.packageJson.version
: 'main'
export const Settings = () => {
const navigate = useNavigate()

View File

@ -169,7 +169,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -180,7 +180,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -191,7 +191,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -443,7 +443,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -643,7 +643,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -654,7 +654,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -709,7 +709,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
"synstructure",
]
@ -724,7 +724,7 @@ dependencies = [
[[package]]
name = "derive-docs"
version = "0.1.24"
version = "0.1.23"
dependencies = [
"Inflector",
"anyhow",
@ -738,7 +738,7 @@ dependencies = [
"rustfmt-wrapper",
"serde",
"serde_tokenstream",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -749,7 +749,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -776,7 +776,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -948,7 +948,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -1038,7 +1038,7 @@ dependencies = [
"inflections",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -1397,7 +1397,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.6"
version = "0.2.5"
dependencies = [
"anyhow",
"approx",
@ -1428,6 +1428,7 @@ dependencies = [
"parse-display",
"pretty_assertions",
"pyo3",
"recursion",
"reqwest",
"ropey",
"schemars",
@ -1462,12 +1463,12 @@ dependencies = [
"pretty_assertions",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
name = "kcl-test-server"
version = "0.1.8"
version = "0.1.7"
dependencies = [
"anyhow",
"hyper",
@ -1841,7 +1842,7 @@ dependencies = [
"regex",
"regex-syntax 0.8.3",
"structmeta",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -1894,7 +1895,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2058,7 +2059,7 @@ dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2071,7 +2072,7 @@ dependencies = [
"proc-macro2",
"pyo3-build-config",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2148,6 +2149,12 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "recursion"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f705426858ccd7bbfe19798239d6b6bfd9bf96bde0624a84b92694046e98871"
[[package]]
name = "redox_syscall"
version = "0.2.16"
@ -2533,7 +2540,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2607,7 +2614,7 @@ checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2618,7 +2625,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2642,7 +2649,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2663,7 +2670,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2800,7 +2807,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2811,7 +2818,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2855,9 +2862,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.75"
version = "2.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7"
dependencies = [
"proc-macro2",
"quote",
@ -2878,7 +2885,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -2985,7 +2992,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -3056,9 +3063,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.39.3"
version = "1.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
dependencies = [
"backtrace",
"bytes",
@ -3080,7 +3087,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -3233,7 +3240,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -3261,7 +3268,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -3338,7 +3345,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
"termcolor",
]
@ -3502,7 +3509,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]
@ -3563,7 +3570,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
"wasm-bindgen-shared",
]
@ -3598,7 +3605,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -3924,7 +3931,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn 2.0.74",
]
[[package]]

View File

@ -17,7 +17,7 @@ gloo-utils = "0.2.0"
kcl-lib = { path = "kcl" }
kittycad.workspace = true
serde_json = "1.0.125"
tokio = { version = "1.39.3", features = ["sync"] }
tokio = { version = "1.39.2", features = ["sync"] }
toml = "0.8.19"
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
wasm-bindgen = "0.2.91"
@ -30,7 +30,7 @@ image = { version = "0.25.1", default-features = false, features = ["png"] }
kittycad = { workspace = true, default-features = true }
pretty_assertions = "1.4.0"
reqwest = { version = "0.11.26", default-features = false }
tokio = { version = "1.39.3", features = ["rt-multi-thread", "macros", "time"] }
tokio = { version = "1.39.2", features = ["rt-multi-thread", "macros", "time"] }
twenty-twenty = "0.8"
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.24"
version = "0.1.23"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -20,7 +20,7 @@ quote = "1"
regex = "1.10"
serde = { version = "1.0.208", features = ["derive"] }
serde_tokenstream = "0.2"
syn = { version = "2.0.75", features = ["full"] }
syn = { version = "2.0.74", features = ["full"] }
[dev-dependencies]
anyhow = "1.0.86"

View File

@ -15,7 +15,7 @@ databake = "0.1.8"
kcl-lib = { path = "../kcl" }
proc-macro2 = "1"
quote = "1"
syn = { version = "2.0.75", features = ["full"] }
syn = { version = "2.0.74", features = ["full"] }
[dev-dependencies]
pretty_assertions = "1.4.0"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-test-server"
description = "A test server for KCL"
version = "0.1.8"
version = "0.1.7"
edition = "2021"
license = "MIT"
@ -12,4 +12,4 @@ kcl-lib = { version = "0.2", path = "../kcl" }
pico-args = "0.5.0"
serde = { version = "1.0.208", features = ["derive"] }
serde_json = "1.0.125"
tokio = { version = "1.39.3", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1.39.2", features = ["macros", "rt-multi-thread"] }

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.6"
version = "0.2.5"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -20,7 +20,7 @@ clap = { version = "4.5.16", default-features = false, optional = true }
convert_case = "0.6.0"
dashmap = "6.0.1"
databake = { version = "0.1.8", features = ["derive"] }
derive-docs = { version = "0.1.24", path = "../derive-docs" }
derive-docs = { version = "0.1.23", path = "../derive-docs" }
form_urlencoded = "1.2.1"
futures = { version = "0.3.30" }
git_rev = "0.1.0"
@ -31,6 +31,7 @@ lazy_static = "1.5.0"
mime_guess = "2.0.5"
parse-display = "0.9.1"
pyo3 = { version = "0.22.2", optional = true }
recursion = "0.5.2"
reqwest = { version = "0.11.26", default-features = false, features = ["stream", "rustls-tls"] }
ropey = "1.6.1"
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"] }
@ -50,7 +51,7 @@ zip = { version = "2.0.0", default-features = false }
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = { version = "0.3.69" }
tokio = { version = "1.39.3", features = ["sync", "time"] }
tokio = { version = "1.39.2", features = ["sync", "time"] }
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
wasm-bindgen = "0.2.91"
wasm-bindgen-futures = "0.4.42"
@ -59,7 +60,7 @@ web-sys = { version = "0.3.69", features = ["console"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
approx = "0.5"
bson = { version = "2.11.0", features = ["uuid-1", "chrono"] }
tokio = { version = "1.39.3", features = ["full"] }
tokio = { version = "1.39.2", features = ["full"] }
tokio-tungstenite = { version = "0.23.1", features = ["rustls-tls-native-roots"] }
tower-lsp = { version = "0.20.0", features = ["proposed"] }

View File

@ -1,2 +1,3 @@
pub mod modify;
mod recursion;
pub mod types;

View File

@ -0,0 +1,167 @@
use recursion::{Collapsible, MappableFrame, PartiallyApplied};
use super::types::{
BinaryExpression, BinaryOperator, BinaryPart, Digest, Expr, FnArgType, Identifier, KclNone, Literal, Parameter,
PipeSubstitution, TagDeclarator,
};
pub enum ExprFrame<A> {
Literal(Box<Literal>),
Identifier(Box<Identifier>),
TagDeclarator(Box<TagDeclarator>),
BinaryExpression(Box<BinaryExpressionFrame<A>>),
FunctionExpression(Box<FunctionExpressionFrame<A>>),
CallExpression(Box<CallExpressionFrame<A>>),
PipeExpression(Box<PipeExpressionFrame<A>>),
PipeSubstitution(Box<PipeSubstitution>),
ArrayExpression(Box<ArrayExpressionFrame<A>>),
ObjectExpression(Box<ObjectExpressionFrame<A>>),
MemberExpression(Box<MemberExpressionFrame<A>>),
UnaryExpression(Box<UnaryExpressionFrame<A>>),
None(KclNone),
}
impl MappableFrame for ExprFrame<PartiallyApplied> {
type Frame<X> = ExprFrame<X>;
fn map_frame<A, B>(input: Self::Frame<A>, mut f: impl FnMut(A) -> B) -> Self::Frame<B> {
match input {
ExprFrame::Literal(x) => ExprFrame::Literal(x),
ExprFrame::Identifier(x) => ExprFrame::Identifier(x),
ExprFrame::TagDeclarator(x) => ExprFrame::TagDeclarator(x),
ExprFrame::BinaryExpression(x) => ExprFrame::BinaryExpression(MappableFrame::map_frame(x, &mut f)),
ExprFrame::FunctionExpression(x) => ExprFrame::FunctionExpression(MappableFrame::map_frame(x, &mut f)),
ExprFrame::CallExpression(x) => ExprFrame::CallExpression(MappableFrame::map_frame(x, &mut f)),
ExprFrame::PipeExpression(x) => ExprFrame::PipeExpression(MappableFrame::map_frame(x, &mut f)),
ExprFrame::PipeSubstitution(x) => ExprFrame::PipeSubstitution(x),
ExprFrame::ArrayExpression(x) => ExprFrame::ArrayExpression(MappableFrame::map_frame(x, &mut f)),
ExprFrame::ObjectExpression(x) => ExprFrame::ObjectExpression(MappableFrame::map_frame(x, &mut f)),
ExprFrame::MemberExpression(x) => ExprFrame::MemberExpression(MappableFrame::map_frame(x, &mut f)),
ExprFrame::UnaryExpression(x) => ExprFrame::UnaryExpression(MappableFrame::map_frame(x, &mut f)),
ExprFrame::None(x) => ExprFrame::None(x),
}
}
}
impl<'a> Collapsible for &'a Expr {
type FrameToken = ExprFrame<PartiallyApplied>;
fn into_frame(self) -> <Self::FrameToken as MappableFrame>::Frame<Self> {
match self {
Expr::Literal(x) => ExprFrame::Literal(x.clone()),
Expr::Identifier(x) => ExprFrame::Identifier(x.clone()),
Expr::TagDeclarator(x) => ExprFrame::TagDeclarator(x.clone()),
Expr::BinaryExpression(x) => ExprFrame::BinaryExpression(Box::new(x.into_frame())),
Expr::FunctionExpression(x) => ExprFrame::FunctionExpression(Box::new(x.into_frame())),
Expr::CallExpression(x) => ExprFrame::CallExpression(Box::new(x.into_frame())),
Expr::PipeExpression(x) => ExprFrame::PipeExpression(Box::new(x.into_frame())),
Expr::PipeSubstitution(x) => ExprFrame::PipeSubstitution(x.clone()),
Expr::ArrayExpression(x) => ExprFrame::ArrayExpression(Box::new(x.into_frame())),
Expr::ObjectExpression(x) => ExprFrame::ObjectExpression(Box::new(x.into_frame())),
Expr::MemberExpression(x) => ExprFrame::MemberExpression(Box::new(x.into_frame())),
Expr::UnaryExpression(x) => ExprFrame::UnaryExpression(Box::new(x.into_frame())),
Expr::None(x) => ExprFrame::None(x.clone()),
}
}
}
pub struct BinaryExpressionFrame<A> {
pub start: usize,
pub end: usize,
pub operator: BinaryOperator,
pub left: BinaryPartFrame<A>,
pub right: BinaryPartFrame<A>,
pub digest: Option<Digest>,
}
impl MappableFrame for BinaryExpressionFrame<PartiallyApplied> {
type Frame<X> = BinaryExpressionFrame<X>;
fn map_frame<A, B>(input: Self::Frame<A>, mut f: impl FnMut(A) -> B) -> Self::Frame<B> {
BinaryExpressionFrame::<B> {
start: input.start,
end: input.end,
operator: input.operator,
left: <BinaryPartFrame<PartiallyApplied> as MappableFrame>::map_frame(input.left, &mut f),
right: <BinaryPartFrame<PartiallyApplied> as MappableFrame>::map_frame(input.right, &mut f),
digest: input.digest,
}
}
}
impl<'a> Collapsible for &'a BinaryExpression {
type FrameToken = BinaryExpressionFrame<PartiallyApplied>;
fn into_frame(self) -> <Self::FrameToken as MappableFrame>::Frame<Self> {
BinaryExpressionFrame::<BinaryExpression> {
start: self.start,
end: self.end,
operator: self.operator.clone(),
left: self.left.into_frame(),
right: self.right.into_frame(),
digest: self.digest,
}
}
}
pub enum BinaryPartFrame<A> {
Literal(Box<Literal>),
Identifier(Box<Identifier>),
BinaryExpression(Box<BinaryExpressionFrame<A>>),
CallExpression(Box<CallExpressionFrame<A>>),
UnaryExpression(Box<UnaryExpressionFrame<A>>),
MemberExpression(Box<MemberExpressionFrame<A>>),
}
impl MappableFrame for BinaryPartFrame<PartiallyApplied> {
type Frame<X> = BinaryPartFrame<X>;
fn map_frame<A, B>(input: Self::Frame<A>, mut f: impl FnMut(A) -> B) -> Self::Frame<B> {
match input {
BinaryPartFrame::Literal(x) => BinaryPartFrame::Literal(x),
BinaryPartFrame::Identifier(x) => BinaryPartFrame::Identifier(x),
BinaryPartFrame::BinaryExpression(x) => BinaryPartFrame::BinaryExpression(MappableFrame::map_frame(x, f)),
BinaryPartFrame::CallExpression(x) => BinaryPartFrame::CallExpression(MappableFrame::map_frame(x, f)),
BinaryPartFrame::UnaryExpression(x) => BinaryPartFrame::UnaryExpression(MappableFrame::map_frame(x, f)),
BinaryPartFrame::MemberExpression(x) => BinaryPartFrame::MemberExpression(MappableFrame::map_frame(x, f)),
}
}
}
impl<'a> Collapsible for &'a BinaryPart {
type FrameToken = BinaryPartFrame<PartiallyApplied>;
fn into_frame(self) -> <Self::FrameToken as MappableFrame>::Frame<Self> {
match self {
BinaryPart::Literal(x) => BinaryPartFrame::Literal(x.clone()),
BinaryPart::Identifier(x) => BinaryPartFrame::Identifier(x.clone()),
BinaryPart::BinaryExpression(x) => BinaryPartFrame::BinaryExpression(x.into_frame()),
BinaryPart::CallExpression(x) => BinaryPartFrame::CallExpression(x.into_frame()),
BinaryPart::UnaryExpression(x) => BinaryPartFrame::UnaryExpression(x.into_frame()),
BinaryPart::MemberExpression(x) => BinaryPartFrame::MemberExpression(x.into_frame()),
}
}
}
pub struct FunctionExpressionFrame<A> {
pub start: usize,
pub end: usize,
pub params: Vec<Parameter>,
pub body: ProgramFrame<A>,
pub return_type: Option<FnArgType>,
pub digest: Option<Digest>,
}
pub struct CallExpressionFrame<A> {
pub start: usize,
pub end: usize,
pub callee: Identifier,
pub arguments: Vec<ExprFrame<A>>,
pub optional: bool,
pub digest: Option<Digest>,
}
// More...

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