Compare commits

...

35 Commits

Author SHA1 Message Date
6e10f75ff6 KCL executor: accept and send 'replay' flag, track session data (#3646)
Part of https://github.com/KittyCAD/engine/issues/2504:

The engine accepts this 'replay' flag now, so, accept it too and send it up to the engine.

Part of https://github.com/KittyCAD/cli/issues/847

The engine sends 'session data' now (like the API Call ID). The CLI executes KCL using this executor, and would like to get the session data after execution.
2024-08-23 17:40:30 -05:00
03e289af20 Fix Commands button to show correct shortcut on Windows and Linux (#3625)
* Fix Commands button to show correct shortcut

* Fix onboarding to use the same shortcut reference

* Rename test file to be more general

* Add test for commands button text

* Remove outdated reference to Ctrl+/

* Change shortcut separator to be + and no spaces

* Add JSDocs and improve comments

* Add unit tests

* Change control modifier to regular ASCII caret

* Add browser test and fix platform detection

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* Add useful debug info to the error message

* Fix to display metaKey as Super on Linux

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)"

This reverts commit f8da90d5d2.

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* Approve snapshots

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-08-23 16:20:22 -04:00
efc140abbf Test: Can load a file with CRLF line endings (#3636)
* Test: Can load a file with CRLF line endings #3616

* first arg stuff??

* Fix paths in playwright for windows

* Fix line ending replace on windows

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

---------

Co-authored-by: Adam Sunderland <iterion@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Adam Sunderland <adam@kittycad.io>
2024-08-23 13:51:30 -04:00
4dfad19b7e add hollow (#3642)
* add hollow

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-08-23 16:57:02 +00:00
e56c634b35 Fix existing: Zoom should be consistent when exiting or entering sketches (#3638)
#3637
2024-08-23 16:10:46 +00:00
00292abc98 Bump @babel/preset-env from 7.25.3 to 7.25.4 (#3630)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.25.3 to 7.25.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.4/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-23 09:00:56 -07:00
483d6903d6 Bump @kittycad/lib from 2.0.0 to 2.0.1 (#3631)
Bumps [@kittycad/lib](https://github.com/KittyCAD/kittycad.ts) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/KittyCAD/kittycad.ts/releases)
- [Commits](https://github.com/KittyCAD/kittycad.ts/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: "@kittycad/lib"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-23 09:00:48 -07:00
3780996374 Fix opening bottom right version link in external browser (#3633) 2024-08-23 09:00:18 -07:00
2fde71228a Bump quote from 1.0.36 to 1.0.37 in /src/wasm-lib (#3628)
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.36 to 1.0.37.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.36...1.0.37)

---
updated-dependencies:
- dependency-name: quote
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-23 08:48:53 -07:00
5cd8ab3812 Enable all disabled win32 tests (#3618)
* Enable all disabled win32 tests

* Skip one test
2024-08-23 10:50:40 -04:00
9a385fb474 Release kcl-lib 0.2.7 (#3641) 2024-08-23 08:11:37 -05:00
b740d25bbd Bump kittycad to 0.3.16 (#3626)
Bump kittycad
2024-08-23 07:50:30 -05:00
ef350b020b file in the file tree open with a single click [fix me remove] (#3635) 2024-08-23 10:29:12 +00:00
4d2375faac tests for file manager the stuff that should happen on disk (#3634)
tests for file manager the stuff that should happen on disk #3587
2024-08-23 17:11:17 +10:00
22a9f44916 fix coredump home page (#3624)
updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-08-22 17:18:29 -07:00
713a30ed72 Fix initial default app settings behavior when the user has no settings yet (#3601)
Fix initial default app settings behavior when the user has no settings yet.

Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-08-22 16:51:26 -07:00
ebed10bc76 Franknoirot/fix file deletion (#3569)
* Don't chop off file name from file path

* Add a test to confirm file deletion works (as long as you have a main.kcl)

* Add TODO test for when main.kcl doesn't exist

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* Make the bad prompt test generate a new prompt each run

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-08-22 19:42:21 -04:00
acbe92d717 Fix keyboard shortcuts to use Control on Windows and Linux (#3620)
* Fix keyboard shortcuts to use Control instead of Meta on Windows and Linux

* Convert more tests to use Playwright built-in
2024-08-22 16:13:27 -07:00
e624c9b124 fix failing tests from chalmers pr (#3619)
* fix failing tests from chalmers pr

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix memory pane

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-08-22 15:00:20 -07:00
877eb3ec5e Lock the rust toolchain version (#3617) 2024-08-22 13:09:28 -07:00
64500d055a Add safer isArray function (#3606)
* Add safer isArray function

* Fix formatting
2024-08-22 13:08:49 -07:00
5df996d877 Bump @types/node from 22.4.2 to 22.5.0 (#3611)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.4.2 to 22.5.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 13:03:44 -07:00
84d70751af Bump husky from 9.1.4 to 9.1.5 (#3592)
Bumps [husky](https://github.com/typicode/husky) from 9.1.4 to 9.1.5.
- [Release notes](https://github.com/typicode/husky/releases)
- [Commits](https://github.com/typicode/husky/compare/v9.1.4...v9.1.5)

---
updated-dependencies:
- dependency-name: husky
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 13:03:29 -07:00
3899999465 Bump vite from 5.3.5 to 5.4.2 (#3591)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.5 to 5.4.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 13:03:16 -07:00
9f370fbb56 default to production when no NODE_ENV (#3614)
* default to production when no NODE_ENV

* tweak message

---------

Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-08-22 13:02:23 -07:00
f750c4ea8b Nadro/3394/project name regression (#3609)
* exporting createProjectAndRenameIt, going to be used for a third time

* feat: added e2e test to test project name retention after onboarding plays

* fix: formatting, adding Page type

* fix: resolved linter warnings, wrong syntax and function name typo
2024-08-22 13:00:13 -07:00
e16ecc28a3 KCL parser: Allow comments in multi-line object expression (#3607)
Like my previous PR to array expressions (https://github.com/KittyCAD/modeling-app/pull/3539), but for object expressions. Closes https://github.com/KittyCAD/modeling-app/issues/1528
2024-08-22 13:54:59 -05:00
a2d8c5a714 Fix path splitting issues on windows (#3565)
* Fix path splitting issues on windows

* Fix path splitting issue on routeLoaders

* Enable some e2e tests

* Swap enabled e2e tests

* Working bare-min project parse

* Make tsc happy

* Clean up & enable more tests

* Fix paths in browser

* Fix tests for windows

fmt

* Clean up wasm side

* Make build:wasm windows compatible

* More paths cleanup & some tests

* Remove sleep

* Use new config sturcture in parseroute

* Clean up debugger

* Fix: on settings close go back to the same file (#3549)

* Fix: on settings close go back to the same file

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* shit aint working yo

* Get that page a-loading

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>

* Fmt

* Comment out currently failing win32 tests

* Ignore tsc for electron monkey-patch

* Force line-endings to only

* Fix tsc

* Enable more tests

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* Avoid modifying global for tests

---------

Co-authored-by: 49fl <ircsurfer33@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-08-22 13:38:53 -04:00
0bb4586e6d enable Playwright list reporter (#3411)
enable list reporter

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2024-08-22 20:51:43 +10:00
bbabf04ba6 electron icons (#3613)
* Add back in app icons that got wiped from Tauri version of the app
#3526

* linux

* windows icon size

* clean up
2024-08-22 17:51:18 +10:00
37a1208924 Fix existing: Extrude from command bar selects extrude line after (#3547)
* Fix existing: Extrude from command bar selects extrude line after #3545

* remove as

---------

Co-authored-by: Frank Noirot <frank@zoo.dev>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-08-22 16:13:08 +10:00
682099c1ad Bump phonenumber in fuzz (#3610) 2024-08-21 16:01:49 -05:00
8f3ad0d43c Bump phonenumber (#3608)
but then who bumped phone????
2024-08-21 14:52:15 -05:00
be047f5111 add unit functions (#3604)
* add unit functions

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* update docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* empty

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-21 12:12:56 -07:00
d656a389f8 Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.

Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.

Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.

My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
227 changed files with 9624 additions and 1708 deletions

BIN
assets/icon.icns Normal file

Binary file not shown.

BIN
assets/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

BIN
assets/icon@2x.icns Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

38
docs/kcl/cm.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

38
docs/kcl/ft.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

835
docs/kcl/hollow.md Normal file

File diff suppressed because one or more lines are too long

38
docs/kcl/inch.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -32,17 +32,21 @@ layout: manual
* [`chamfer`](kcl/chamfer)
* [`circle`](kcl/circle)
* [`close`](kcl/close)
* [`cm`](kcl/cm)
* [`cos`](kcl/cos)
* [`e`](kcl/e)
* [`extrude`](kcl/extrude)
* [`fillet`](kcl/fillet)
* [`floor`](kcl/floor)
* [`ft`](kcl/ft)
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
* [`getOppositeEdge`](kcl/getOppositeEdge)
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
* [`helix`](kcl/helix)
* [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`import`](kcl/import)
* [`inch`](kcl/inch)
* [`int`](kcl/int)
* [`lastSegX`](kcl/lastSegX)
* [`lastSegY`](kcl/lastSegY)
@ -55,8 +59,10 @@ layout: manual
* [`log`](kcl/log)
* [`log10`](kcl/log10)
* [`log2`](kcl/log2)
* [`m`](kcl/m)
* [`max`](kcl/max)
* [`min`](kcl/min)
* [`mm`](kcl/mm)
* [`patternCircular2d`](kcl/patternCircular2d)
* [`patternCircular3d`](kcl/patternCircular3d)
* [`patternLinear2d`](kcl/patternLinear2d)
@ -89,3 +95,4 @@ layout: manual
* [`xLineTo`](kcl/xLineTo)
* [`yLine`](kcl/yLine)
* [`yLineTo`](kcl/yLineTo)
* [`yd`](kcl/yd)

File diff suppressed because one or more lines are too long

38
docs/kcl/m.md Normal file

File diff suppressed because one or more lines are too long

38
docs/kcl/mm.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

38
docs/kcl/yd.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,39 @@ test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test.describe('Electron user sidebar menu tests', () => {
test.describe('Electron app header tests', () => {
test(
'Open Command Palette button has correct shortcut',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async () => {},
})
await page.setViewportSize({ width: 1200, height: 500 })
// No space before the shortcut since it checks textContent.
let text
switch (process.platform) {
case 'darwin':
text = 'Commands⌘K'
break
case 'win32':
text = 'CommandsCtrl+K'
break
default: // 'linux' etc.
text = 'CommandsCtrl+K'
break
}
const commandsButton = page.getByRole('button', { name: 'Commands' })
await expect(commandsButton).toBeVisible()
await expect(commandsButton).toHaveText(text)
await electronApp.close()
}
)
test(
'User settings has correct shortcut',
{ tag: '@electron' },

View File

@ -1,6 +1,13 @@
import { test, expect } from '@playwright/test'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import {
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { join } from 'path'
import { bracket } from 'lib/exampleKcl'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
import fsp from 'fs/promises'
@ -223,26 +230,24 @@ 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) => {
const routerTemplateDir = join(dir, 'router-template-slate')
const bracketDir = join(dir, 'bracket')
await Promise.all([
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
fsp.mkdir(routerTemplateDir, { recursive: true }),
fsp.mkdir(bracketDir, { recursive: true }),
])
await Promise.all([
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/router-template-slate/main.kcl`
executorInputPath('router-template-slate.kcl'),
join(routerTemplateDir, 'main.kcl')
),
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
),
])
},

View File

@ -12,50 +12,47 @@ test.afterEach(async ({ page }, testInfo) => {
})
test.describe('Command bar tests', () => {
// TODO fixme: enter is not working in the command bar
test.fixme(
'Extrude from command bar selects extrude line after',
async ({ page }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const sketch001 = startSketchOn('XY')
test('Extrude from command bar selects extrude line after', async ({
page,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> xLine(-20, %)
|> close(%)
`
)
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// Click the line of code for xLine.
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Extrude' }).click()
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
await page.waitForTimeout(200)
await expect(page.locator('.cm-activeLine')).toHaveText(
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
)
}
)
})
// TODO fixme: enter is not working in the command bar
test.fixme('Fillet from command bar', async ({ page }) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// Click the line of code for xLine.
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Extrude' }).click()
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
await page.waitForTimeout(200)
await expect(page.locator('.cm-activeLine')).toHaveText(
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
)
})
test('Fillet from command bar', async ({ page }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
@ -127,7 +124,7 @@ const extrude001 = extrude(-10, sketch001)`
await expect(cmdSearchBar).not.toBeVisible()
// Now try the same, but with the keyboard shortcut, check focus
await page.keyboard.press('Meta+K')
await page.keyboard.press('ControlOrMeta+K')
await expect(cmdSearchBar).toBeVisible()
await expect(cmdSearchBar).toBeFocused()
@ -188,7 +185,7 @@ const extrude001 = extrude(-10, sketch001)`
await page.locator('.cm-content').click()
// Now try the same, but with the keyboard shortcut, check focus
await page.keyboard.press('Meta+K')
await page.keyboard.press('ControlOrMeta+K')
let cmdSearchBar = page.getByPlaceholder('Search commands')
await expect(cmdSearchBar).toBeVisible()
@ -253,7 +250,7 @@ const extrude001 = extrude(-10, sketch001)`
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
let cmdSearchBar = page.getByPlaceholder('Search commands')
await page.keyboard.press('Meta+K')
await page.keyboard.press('ControlOrMeta+K')
await expect(cmdSearchBar).toBeVisible()
// Search for extrude command and choose it

View File

@ -332,7 +332,6 @@ test.describe('Copilot ghost text', () => {
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
await u.codeLocator.click()
await expect(page.locator('.cm-content')).toHaveText(``)
@ -349,10 +348,10 @@ test.describe('Copilot ghost text', () => {
)
// Going elsewhere in the code should hide the ghost text.
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.down('Shift')
await page.keyboard.press('KeyZ')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
@ -368,8 +367,6 @@ test.describe('Copilot ghost text', () => {
await u.waitForAuthSkipAppStart()
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
await page.waitForTimeout(800)
await u.codeLocator.click()
await expect(page.locator('.cm-content')).toHaveText(``)
@ -382,17 +379,17 @@ test.describe('Copilot ghost text', () => {
await page.waitForTimeout(800)
// Ctrl+z
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-content')).toHaveText(``)
// Ctrl+shift+z
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.down('Shift')
await page.keyboard.press('KeyZ')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`)
@ -411,14 +408,14 @@ test.describe('Copilot ghost text', () => {
)
// Once for the enter.
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
// Once for the text.
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()

View File

@ -1,5 +1,11 @@
import { test, expect } from '@playwright/test'
import { getUtils, setupElectron, tearDown } from './test-utils'
import { join } from 'path'
import {
getUtils,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import fsp from 'fs/promises'
test.afterEach(async ({ page }, testInfo) => {
@ -10,22 +16,19 @@ 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 })])
const bracketDir = join(dir, 'bracket')
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
await Promise.all([
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/bracket/other.kcl`
executorInputPath('router-template-slate.kcl'),
join(bracketDir, 'other.kcl')
),
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
),
])
},

View File

@ -16,7 +16,6 @@ test.describe('Editor tests', () => {
await page.setViewportSize({ width: 1000, height: 500 })
await u.waitForAuthSkipAppStart()
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
// check no error to begin with
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
@ -29,9 +28,9 @@ test.describe('Editor tests', () => {
|> line([-20, 0], %)
|> close(%)`)
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('/')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XY')
@ -42,9 +41,9 @@ test.describe('Editor tests', () => {
// |> close(%)`)
// uncomment the code
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('/')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XY')
@ -148,9 +147,7 @@ test.describe('Editor tests', () => {
// Delete all the code.
await page.locator('.cm-content').click()
// Select all
await page.keyboard.press('Control+A')
await page.keyboard.press('Backspace')
await page.keyboard.press('Meta+A')
await page.keyboard.press('ControlOrMeta+A')
await page.keyboard.press('Backspace')
await expect(page.locator('.cm-content')).toHaveText(``)

View File

@ -1,5 +1,6 @@
import { test, expect } from '@playwright/test'
import { setupElectron, tearDown } from './test-utils'
import { setupElectron, tearDown, executorInputPath } from './test-utils'
import { join } from 'path'
import fsp from 'fs/promises'
test.afterEach(async ({ page }, testInfo) => {
@ -10,17 +11,14 @@ 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 })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})
@ -58,17 +56,14 @@ 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 })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})

View File

@ -1,6 +1,13 @@
import { test, expect } from '@playwright/test'
import { join } from 'path'
import fsp from 'fs/promises'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import {
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { bracket } from 'lib/exampleKcl'
import { onboardingPaths } from 'routes/Onboarding/paths'
import {
@ -347,17 +354,14 @@ 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) => {
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
const routerTemplateDir = join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/router-template-slate/main.kcl`
executorInputPath('router-template-slate.kcl'),
join(routerTemplateDir, 'main.kcl')
)
},
})

View File

@ -1,11 +1,13 @@
import { test, expect } from '@playwright/test'
import { test, expect, Page } from '@playwright/test'
import {
doExport,
executorInputPath,
getUtils,
isOutOfViewInScrollContainer,
Paths,
setupElectron,
tearDown,
createProjectAndRenameIt,
} from './test-utils'
import fsp from 'fs/promises'
import fs from 'fs'
@ -45,17 +47,14 @@ 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) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})
@ -64,8 +63,6 @@ test(
page.on('console', console.log)
page.on('console', console.log)
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
@ -92,17 +89,13 @@ 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.mkdir(join(dir, 'broken-code'), { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/broken-code-test.kcl',
`${dir}/broken-code/main.kcl`
executorInputPath('broken-code-test.kcl'),
join(dir, 'broken-code', 'main.kcl')
)
},
})
@ -138,17 +131,14 @@ test.describe('Can export from electron app', () => {
`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 })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})
@ -232,10 +222,6 @@ 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) => {
@ -468,7 +454,7 @@ test(
await electronApp.close()
}
)
test.fixme(
test(
'File in the file pane should open with a single click',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
@ -524,10 +510,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,
})
@ -535,33 +517,23 @@ test(
page.on('console', console.log)
const createProjectAndRenameIt = async (name: string) =>
test.step(`Create and rename project ${name}`, async () => {
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText(`project-000`)).toBeVisible()
await page.getByText(`project-000`).hover()
await page.getByText(`project-000`).focus()
await page.getByLabel('sketch').first().click()
await page.waitForTimeout(100)
// type "updated project name"
await page.keyboard.press('Backspace')
await page.keyboard.type(name)
await page.getByLabel('checkmark').last().click()
const createProjectAndRenameItTest = async ({
name,
page,
}: {
name: string
page: Page
}) => {
await test.step(`Create and rename project ${name}`, async () => {
await createProjectAndRenameIt({ name, page })
})
}
// we need to create the folders so that the order is correct
// creating them ahead of time with fs tools means they all have the same timestamp
await createProjectAndRenameIt('router-template-slate')
// await createProjectAndRenameIt('focusrite_scarlett_mounting_braket')
await createProjectAndRenameIt('bracket')
await createProjectAndRenameIt('lego')
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
await createProjectAndRenameItTest({ name: 'bracket', page })
await createProjectAndRenameItTest({ name: 'lego', page })
await test.step('delete the middle project, i.e. the bracket project', async () => {
const project = page.getByText('bracket')
@ -618,14 +590,52 @@ test(
}
)
test(
'Can load a file with CRLF line endings',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const routerTemplateDir = join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true })
const file = await fsp.readFile(
executorInputPath('router-template-slate.kcl'),
'utf-8'
)
// Replace both \r optionally so we don't end up with \r\r\n
const fileWithCRLF = file.replace(/\r?\n/g, '\r\n')
await fsp.writeFile(
join(routerTemplateDir, 'main.kcl'),
fileWithCRLF,
'utf-8'
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
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(u.codeLocator).toContainText('routerDiameter')
await expect(u.codeLocator).toContainText('templateGap')
await expect(u.codeLocator).toContainText('minClampingDistance')
await electronApp.close()
}
)
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,
})
@ -635,33 +645,23 @@ test(
page.on('console', console.log)
const createProjectAndRenameIt = async (name: string) =>
test.step(`Create and rename project ${name}`, async () => {
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText(`project-000`)).toBeVisible()
await page.getByText(`project-000`).hover()
await page.getByText(`project-000`).focus()
await page.getByLabel('sketch').first().click()
await page.waitForTimeout(100)
// type "updated project name"
await page.keyboard.press('Backspace')
await page.keyboard.type(name)
await page.getByLabel('checkmark').last().click()
const createProjectAndRenameItTest = async ({
name,
page,
}: {
name: string
page: Page
}) => {
await test.step(`Create and rename project ${name}`, async () => {
await createProjectAndRenameIt({ name, page })
})
}
// we need to create the folders so that the order is correct
// creating them ahead of time with fs tools means they all have the same timestamp
await createProjectAndRenameIt('router-template-slate')
// await createProjectAndRenameIt('focusrite_scarlett_mounting_braket')
await createProjectAndRenameIt('bracket')
await createProjectAndRenameIt('lego')
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
await createProjectAndRenameItTest({ name: 'bracket', page })
await createProjectAndRenameItTest({ name: 'lego', page })
await test.step('should be shorted by modified initially', async () => {
const lastModifiedButton = page.getByRole('button', {
@ -748,10 +748,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,25 +832,35 @@ 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) => {
await Promise.all([
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
fsp.mkdir(join(dir, 'router-template-slate'), { recursive: true }),
fsp.mkdir(join(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`
join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs',
'router-template-slate.kcl'
),
join(dir, 'router-template-slate', 'main.kcl')
),
fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
join(
'src',
'wasm-lib',
'tests',
'executor',
'inputs',
'focusrite_scarlett_mounting_braket.kcl'
),
join(dir, 'bracket', 'main.kcl')
),
])
},
@ -1053,10 +1059,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'],
@ -1071,7 +1073,7 @@ test(
for (const [name, file] of projectData) {
await fsp.mkdir(join(dir, name), { recursive: true })
await fsp.copyFile(
join('src', 'wasm-lib', 'tests', 'executor', 'inputs', file),
executorInputPath(file),
join(dir, name, `main.kcl`)
)
}
@ -1118,14 +1120,11 @@ 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 testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
const fileNames = [
'angled_line.kcl',
'basic_fillet_cube_close_opposite.kcl',
@ -1189,8 +1188,8 @@ test(
]
for (const fileName of fileNames) {
await fsp.copyFile(
`src/wasm-lib/tests/executor/inputs/${fileName}`,
`${dir}/testProject/${fileName}`
executorInputPath(fileName),
join(testDir, fileName)
)
}
},
@ -1231,19 +1230,16 @@ 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 })
const testDir = join(dir, name)
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
`src/wasm-lib/tests/executor/inputs/${name}.kcl`,
`${dir}/${name}/main.kcl`
executorInputPath(`${name}.kcl`),
join(testDir, 'main.kcl')
)
},
})
@ -1254,18 +1250,13 @@ test(
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.down('ControlOrMeta')
await page.keyboard.press('KeyA')
await page.keyboard.up(modifier)
await page.keyboard.up('ControlOrMeta')
// check the length of the selected text
const selectedText = await page.evaluate(() => {
@ -1281,9 +1272,9 @@ test(
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.down('ControlOrMeta')
await page.keyboard.press('KeyA')
await page.keyboard.up(modifier)
await page.keyboard.up('ControlOrMeta')
// check the length of the selected text
const selectedText = await page.evaluate(() => {
@ -1302,10 +1293,6 @@ 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,
@ -1350,27 +1337,16 @@ test.describe('Renaming in the file tree', () => {
'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({
const { electronApp, page, dir } = 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'),
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
executorInputPath('cylinder.kcl'),
join(dir, 'Test Project', 'fileToRename.kcl')
)
},
@ -1382,6 +1358,16 @@ test.describe('Renaming in the file tree', () => {
// Constants and locators
const projectLink = page.getByText('Test Project')
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const checkUnRenamedFS = () => {
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
return fs.existsSync(filePath)
}
const newFileName = 'newFileName'
const checkRenamedFS = () => {
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
return fs.existsSync(filePath)
}
const fileToRename = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
@ -1390,7 +1376,6 @@ test.describe('Renaming in the file tree', () => {
.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 () => {
@ -1401,6 +1386,8 @@ test.describe('Renaming in the file tree', () => {
await u.openFilePanel()
await expect(fileToRename).toBeVisible()
expect(checkUnRenamedFS()).toBeTruthy()
expect(checkRenamedFS()).toBeFalsy()
await fileToRename.click()
await expect(projectMenuButton).toContainText('fileToRename.kcl')
await u.openKclCodePanel()
@ -1419,6 +1406,8 @@ test.describe('Renaming in the file tree', () => {
await test.step('Verify the file is renamed', async () => {
await expect(fileToRename).not.toBeAttached()
await expect(renamedFile).toBeVisible()
expect(checkUnRenamedFS()).toBeFalsy()
expect(checkRenamedFS()).toBeTruthy()
})
await test.step('Verify we navigated', async () => {
@ -1442,27 +1431,16 @@ test.describe('Renaming in the file tree', () => {
'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({
const { electronApp, page, dir } = 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'),
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
executorInputPath('cylinder.kcl'),
join(dir, 'Test Project', 'fileToRename.kcl')
)
},
@ -1473,6 +1451,14 @@ test.describe('Renaming in the file tree', () => {
// Constants and locators
const newFileName = 'newFileName'
const checkUnRenamedFS = () => {
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
return fs.existsSync(filePath)
}
const checkRenamedFS = () => {
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
return fs.existsSync(filePath)
}
const projectLink = page.getByText('Test Project')
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const fileToRename = page
@ -1493,6 +1479,8 @@ test.describe('Renaming in the file tree', () => {
await u.openFilePanel()
await expect(fileToRename).toBeVisible()
expect(checkUnRenamedFS()).toBeTruthy()
expect(checkRenamedFS()).toBeFalsy()
})
await test.step('Rename the file', async () => {
@ -1506,6 +1494,8 @@ test.describe('Renaming in the file tree', () => {
await test.step('Verify the file is renamed', async () => {
await expect(fileToRename).not.toBeAttached()
await expect(renamedFile).toBeVisible()
expect(checkUnRenamedFS()).toBeFalsy()
expect(checkRenamedFS()).toBeTruthy()
})
await test.step('Verify we have not navigated', async () => {
@ -1532,30 +1522,19 @@ test.describe('Renaming in the file tree', () => {
`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({
const { electronApp, page, dir } = 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'),
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
executorInputPath('cylinder.kcl'),
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
)
},
@ -1573,8 +1552,17 @@ test.describe('Renaming in the file tree', () => {
})
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
const renameInput = page.getByPlaceholder('folderToRename')
const originalFolderName = 'folderToRename'
const renameInput = page.getByPlaceholder(originalFolderName)
const newFolderName = 'newFolderName'
const checkUnRenamedFolderFS = () => {
const folderPath = join(dir, 'Test Project', originalFolderName)
return fs.existsSync(folderPath)
}
const checkRenamedFolderFS = () => {
const folderPath = join(dir, 'Test Project', newFolderName)
return fs.existsSync(folderPath)
}
await test.step('Open project and file pane', async () => {
await expect(projectLink).toBeVisible()
@ -1588,6 +1576,8 @@ test.describe('Renaming in the file tree', () => {
await u.openFilePanel()
await expect(folderToRename).toBeVisible()
expect(checkUnRenamedFolderFS()).toBeTruthy()
expect(checkRenamedFolderFS()).toBeFalsy()
})
await test.step('Rename the folder', async () => {
@ -1607,6 +1597,8 @@ test.describe('Renaming in the file tree', () => {
await expect(projectMenuButton).toContainText('main.kcl')
await expect(renamedFolder).toBeVisible()
await expect(folderToRename).not.toBeAttached()
expect(checkUnRenamedFolderFS()).toBeFalsy()
expect(checkRenamedFolderFS()).toBeTruthy()
})
await electronApp.close()
@ -1617,12 +1609,7 @@ test.describe('Renaming in the file tree', () => {
`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({
const { electronApp, page, dir } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
@ -1630,11 +1617,11 @@ test.describe('Renaming in the file tree', () => {
recursive: true,
})
await fsp.copyFile(
join(exampleDir, 'basic_fillet_cube_end.kcl'),
executorInputPath('basic_fillet_cube_end.kcl'),
join(dir, 'Test Project', 'main.kcl')
)
await fsp.copyFile(
join(exampleDir, 'cylinder.kcl'),
executorInputPath('cylinder.kcl'),
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
)
},
@ -1655,8 +1642,17 @@ test.describe('Renaming in the file tree', () => {
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
})
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
const renameInput = page.getByPlaceholder('folderToRename')
const originalFolderName = 'folderToRename'
const renameInput = page.getByPlaceholder(originalFolderName)
const newFolderName = 'newFolderName'
const checkUnRenamedFolderFS = () => {
const folderPath = join(dir, 'Test Project', originalFolderName)
return fs.existsSync(folderPath)
}
const checkRenamedFolderFS = () => {
const folderPath = join(dir, 'Test Project', newFolderName)
return fs.existsSync(folderPath)
}
await test.step('Open project and navigate into folder', async () => {
await expect(projectLink).toBeVisible()
@ -1679,9 +1675,12 @@ test.describe('Renaming in the file tree', () => {
expect(newUrl).toContain('folderToRename')
expect(newUrl).toContain('someFileWithin.kcl')
expect(newUrl).not.toContain('main.kcl')
expect(checkUnRenamedFolderFS()).toBeTruthy()
expect(checkRenamedFolderFS()).toBeFalsy()
})
await test.step('Rename the folder', async () => {
await page.waitForTimeout(60000)
await folderToRename.click({ button: 'right' })
await expect(renameMenuItem).toBeVisible()
await renameMenuItem.click()
@ -1704,9 +1703,124 @@ test.describe('Renaming in the file tree', () => {
expect(url).not.toContain('main.kcl')
expect(url).toContain(newFolderName)
expect(url).toContain('someFileWithin.kcl')
expect(checkUnRenamedFolderFS()).toBeFalsy()
expect(checkRenamedFolderFS()).toBeTruthy()
})
await electronApp.close()
}
)
})
test.describe('Deleting files from the file pane', () => {
test(
`when main.kcl exists, navigate to main.kcl`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'fileToDelete.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectCard = page.getByText('testProject')
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const fileToDelete = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'fileToDelete.kcl' }) })
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
const deleteConfirmation = page.getByTestId('delete-confirmation')
await test.step('Open project and navigate to fileToDelete.kcl', async () => {
await projectCard.click()
await u.waitForPageLoad()
await u.openFilePanel()
await fileToDelete.click()
await u.waitForPageLoad()
await u.openKclCodePanel()
await expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
await u.closeKclCodePanel()
})
await test.step('Delete fileToDelete.kcl', async () => {
await fileToDelete.click({ button: 'right' })
await expect(deleteMenuItem).toBeVisible()
await deleteMenuItem.click()
await expect(deleteConfirmation).toBeVisible()
await deleteConfirmation.click()
})
await test.step('Check deletion and navigation', async () => {
await u.waitForPageLoad()
await expect(fileToDelete).not.toBeVisible()
await u.closeFilePanel()
await u.openKclCodePanel()
await expect(u.codeLocator).toContainText('circle(')
await expect(projectMenuButton).toContainText('main.kcl')
})
await electronApp.close()
}
)
test.fixme('TODO - when main.kcl does not exist', async () => {})
})
test(
'Original project name persist after onboarding',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
})
await page.setViewportSize({ width: 1200, height: 500 })
const getAllProjects = () => page.getByTestId('project-link').all()
page.on('console', console.log)
await test.step('Should create and name a project called wrist brace', async () => {
await createProjectAndRenameIt({ name: 'wrist brace', page })
})
await test.step('Should go through onboarding', async () => {
await page.getByTestId('user-sidebar-toggle').click()
await page.getByTestId('user-settings').click()
await page.getByRole('button', { name: 'Replay Onboarding' }).click()
const numberOfOnboardingSteps = 12
for (let clicks = 0; clicks < numberOfOnboardingSteps; clicks++) {
await page.getByTestId('onboarding-next').click()
}
await page.getByTestId('project-sidebar-toggle').click()
})
await test.step('Should go home after onboarding is completed', async () => {
await page.getByRole('button', { name: 'Go to Home' }).click()
})
await test.step('Should show the original project called wrist brace', async () => {
const projectNames = ['Tutorial Project 00', 'wrist brace']
for (const [index, projectLink] of (await getAllProjects()).entries()) {
await expect(projectLink).toContainText(projectNames[index])
}
})
await electronApp.close()
}
)

View File

@ -1,6 +1,13 @@
import { test, expect, Page } from '@playwright/test'
import { join } from 'path'
import * as fsp from 'fs/promises'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import {
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
import { bracket } from 'lib/exampleKcl'
@ -425,17 +432,14 @@ 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) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 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: 46 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: 35 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: 46 KiB

After

Width:  |  Height:  |  Size: 46 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: 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

@ -2,13 +2,13 @@ import {
expect,
Page,
Download,
TestInfo,
BrowserContext,
TestInfo,
_electron as electron,
Locator,
test,
} from '@playwright/test'
import { EngineCommand } from 'lang/std/artifactGraph'
import os from 'os'
import fsp from 'fs/promises'
import fsSync from 'fs'
import { join } from 'path'
@ -26,6 +26,7 @@ import {
import * as TOML from '@iarna/toml'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { SETTINGS_FILE_NAME } from 'lib/constants'
import { isArray } from 'lib/utils'
type TestColor = [number, number, number]
export const TEST_COLORS = {
@ -44,6 +45,9 @@ export const commonPoints = {
num2: 14.44,
}
export const editorSelector = '[role="textbox"][data-language="kcl"]'
type PaneId = 'variables' | 'code' | 'files' | 'logs'
async function waitForPageLoadWithRetry(page: Page) {
await expect(async () => {
await page.goto('/')
@ -73,11 +77,10 @@ async function waitForPageLoad(page: Page) {
}
async function removeCurrentCode(page: Page) {
const hotkey = process.platform === 'darwin' ? 'Meta' : 'Control'
await page.locator('.cm-content').click()
await page.keyboard.down(hotkey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('a')
await page.keyboard.up(hotkey)
await page.keyboard.up('ControlOrMeta')
await page.keyboard.press('Backspace')
await expect(page.locator('.cm-content')).toHaveText('')
}
@ -205,7 +208,7 @@ export const wiggleMove = async (
}
export const circleMove = async (
page: any,
page: Page,
x: number,
y: number,
steps: number,
@ -311,13 +314,19 @@ export function normaliseKclNumbers(code: string, ignoreZero = true): string {
return replaceNumbers(code)
}
export async function getUtils(page: Page) {
export async function getUtils(page: Page, test_?: typeof test) {
if (!test) {
console.warn(
'Some methods in getUtils requires test object as second argument'
)
}
// Chrome devtools protocol session only works in Chromium
const browserType = page.context().browser()?.browserType().name()
const cdpSession =
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
return {
const util = {
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
waitForPageLoad: () => waitForPageLoad(page),
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
@ -484,7 +493,74 @@ export async function getUtils(page: Page) {
networkOptions
)
},
toNormalizedCode: (text: string) => {
return text.replace(/\s+/g, '')
},
createAndSelectProject: async (hasText: string) => {
return test_?.step(
`Create and select project with text "${hasText}"`,
async () => {
await page.getByTestId('home-new-file').click()
const projectLinksPost = page.getByTestId('project-link')
await projectLinksPost.filter({ hasText }).click()
}
)
},
editorTextMatches: async (code: string) => {
const editor = page.locator(editorSelector)
const editorText = await editor.textContent()
return expect(util.toNormalizedCode(editorText || '')).toBe(
util.toNormalizedCode(code)
)
},
pasteCodeInEditor: async (code: string) => {
return test?.step('Paste in KCL code', async () => {
const editor = page.locator(editorSelector)
await editor.fill(code)
await util.editorTextMatches(code)
})
},
clickPane: async (paneId: PaneId) => {
return test?.step(`Open ${paneId} pane`, async () => {
await page.getByTestId(paneId + '-pane-button').click()
await expect(page.locator('#' + paneId + '-pane')).toBeVisible()
})
},
createNewFileAndSelect: async (name: string) => {
return test?.step(`Create a file named ${name}, select it`, async () => {
await page.getByTestId('create-file-button').click()
await page.getByTestId('file-rename-field').fill(name)
await page.keyboard.press('Enter')
await page
.getByTestId('file-pane-scroll-container')
.filter({ hasText: name })
.click()
})
},
panesOpen: async (paneIds: PaneId[]) => {
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
await page.addInitScript(
({ PERSIST_MODELING_CONTEXT, paneIds }) => {
localStorage.setItem(
PERSIST_MODELING_CONTEXT,
JSON.stringify({ openPanes: paneIds })
)
},
{ PERSIST_MODELING_CONTEXT, paneIds }
)
await page.reload()
})
},
}
return util
}
type TemplateOptions = Array<number | Array<number>>
@ -505,7 +581,7 @@ const _makeTemplate = (
templateParts: TemplateStringsArray,
...options: TemplateOptions
) => {
const length = Math.max(...options.map((a) => (Array.isArray(a) ? a[0] : 0)))
const length = Math.max(...options.map((a) => (isArray(a) ? a[0] : 0)))
let reExpTemplate = ''
for (let i = 0; i < length; i++) {
const currentStr = templateParts.map((str, index) => {
@ -513,7 +589,7 @@ const _makeTemplate = (
return (
escapeRegExp(str) +
String(
Array.isArray(currentOptions)
isArray(currentOptions)
? currentOptions[i]
: typeof currentOptions === 'number'
? currentOptions
@ -667,11 +743,6 @@ export const doExport = async (
}
}
/**
* Gets the appropriate modifier key for the platform.
*/
export const metaModifier = os.platform() === 'darwin' ? 'Meta' : 'Control'
export async function tearDown(page: Page, testInfo: TestInfo) {
if (testInfo.status === 'skipped') return
if (testInfo.status === 'failed') return
@ -733,6 +804,7 @@ export async function setup(context: BrowserContext, page: Page) {
// kill animations, speeds up tests and reduced flakiness
await page.emulateMedia({ reducedMotion: 'reduce' })
// Trigger a navigation, since loading file:// doesn't.
await page.reload()
}
@ -793,7 +865,7 @@ export async function setupElectron({
await setup(context, page)
return { electronApp, page }
return { electronApp, page, dir: projectDirName }
}
export async function isOutOfViewInScrollContainer(
@ -814,3 +886,33 @@ export async function isOutOfViewInScrollContainer(
return isOutOfView
}
export async function createProjectAndRenameIt({
name,
page,
}: {
name: string
page: Page
}) {
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText(`project-000`)).toBeVisible()
await page.getByText(`project-000`).hover()
await page.getByText(`project-000`).focus()
await page.getByLabel('sketch').first().click()
await page.waitForTimeout(100)
// type the name passed in
await page.keyboard.press('Backspace')
await page.keyboard.type(name)
await page.getByLabel('checkmark').last().click()
}
export function executorInputPath(fileName: string): string {
return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
}

View File

@ -174,168 +174,166 @@ test.describe('Testing Camera Movement', () => {
}, [0, -85, -85])
})
// TODO fixme something is wrong with sketch here
test.fixme(
'Zoom should be consistent when exiting or entering sketches',
async ({ page }) => {
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
// than again for sketching
test('Zoom should be consistent when exiting or entering sketches', async ({
page,
}) => {
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
// than again for sketching
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
// click on "Start Sketch" button
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
// select a plane
await page.mouse.click(700, 325)
let code = `const sketch001 = startSketchOn('XY')`
await expect(u.codeLocator).toHaveText(code)
await u.closeDebugPanel()
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
// move the camera slightly
await page.keyboard.down('Shift')
await page.mouse.move(700, 300)
await page.mouse.down({ button: 'right' })
await page.mouse.move(800, 200)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Shift')
let y = 350,
x = 948
await u.canvasLocator.click({ position: { x: 783, y } })
code += `\n |> startProfileAt([8.12, -12.98], %)`
// await expect(u.codeLocator).toHaveText(code)
await u.canvasLocator.click({ position: { x, y } })
code += `\n |> line([11.18, 0], %)`
// await expect(u.codeLocator).toHaveText(code)
await u.canvasLocator.click({ position: { x, y: 275 } })
code += `\n |> line([0, 6.99], %)`
// await expect(u.codeLocator).toHaveText(code)
// click the line button
await page.getByRole('button', { name: 'line Line', exact: true }).click()
const hoverOverNothing = async () => {
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
await page.mouse.move(700, 325)
await page.waitForTimeout(100)
// select a plane
await page.mouse.click(700, 325)
let code = `const sketch001 = startSketchOn('XY')`
await expect(u.codeLocator).toHaveText(code)
await u.closeDebugPanel()
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
// move the camera slightly
await page.keyboard.down('Shift')
await page.mouse.move(700, 300)
await page.mouse.down({ button: 'right' })
await page.mouse.move(800, 200)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Shift')
let y = 350,
x = 948
await u.canvasLocator.click({ position: { x: 783, y } })
code += `\n |> startProfileAt([8.12, -12.98], %)`
// await expect(u.codeLocator).toHaveText(code)
await u.canvasLocator.click({ position: { x, y } })
code += `\n |> line([11.18, 0], %)`
// await expect(u.codeLocator).toHaveText(code)
await u.canvasLocator.click({ position: { x, y: 275 } })
code += `\n |> line([0, 6.99], %)`
// await expect(u.codeLocator).toHaveText(code)
// click the line button
await page.getByRole('button', { name: 'line Line', exact: true }).click()
const hoverOverNothing = async () => {
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
await page.mouse.move(700, 325)
await page.waitForTimeout(100)
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
timeout: 10_000,
})
}
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(200)
// hover over horizontal line
await u.canvasLocator.hover({ position: { x: 800, y } })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await page.waitForTimeout(200)
await hoverOverNothing()
await page.waitForTimeout(200)
// hover over vertical line
await u.canvasLocator.hover({ position: { x, y: 325 } })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// click exit sketch
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(400)
await hoverOverNothing()
await page.waitForTimeout(200)
// hover over horizontal line
await page.mouse.move(858, y, { steps: 5 })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// hover over vertical line
await page.mouse.move(x, 325)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// hover over vertical line
await page.mouse.move(857, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
// now click it
await page.mouse.click(857, y)
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
await hoverOverNothing()
x = 975
y = 468
await page.waitForTimeout(100)
await page.mouse.move(x, 419, { steps: 5 })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.mouse.move(855, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(200)
await hoverOverNothing()
await page.waitForTimeout(200)
await page.mouse.move(x, 419)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.mouse.move(855, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
timeout: 10_000,
})
}
)
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(200)
// hover over horizontal line
await u.canvasLocator.hover({ position: { x: 800, y } })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await page.waitForTimeout(200)
await hoverOverNothing()
await page.waitForTimeout(200)
// hover over vertical line
await u.canvasLocator.hover({ position: { x, y: 325 } })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// click exit sketch
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(400)
await hoverOverNothing()
await page.waitForTimeout(200)
// hover over horizontal line
await page.mouse.move(858, y, { steps: 5 })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// hover over vertical line
await page.mouse.move(x, 325)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
// hover over vertical line
await page.mouse.move(857, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
// now click it
await page.mouse.click(857, y)
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
await hoverOverNothing()
x = 975
y = 468
await page.waitForTimeout(100)
await page.mouse.move(x, 419, { steps: 5 })
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.mouse.move(855, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(200)
await hoverOverNothing()
await page.waitForTimeout(200)
await page.mouse.move(x, 419)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
await hoverOverNothing()
await page.mouse.move(855, y)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
timeout: 10_000,
})
})
})

View File

@ -1,6 +1,13 @@
import { test, expect } from '@playwright/test'
import * as fsp from 'fs/promises'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
import { join } from 'path'
import {
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { TEST_SETTINGS_KEY, TEST_SETTINGS_CORRUPTED } from './storageStates'
import * as TOML from '@iarna/toml'
@ -72,7 +79,7 @@ test.describe('Testing settings', () => {
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
// Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('Meta+Shift+,')
await page.keyboard.press('ControlOrMeta+Shift+,')
await expect(headingLocator).toBeVisible()
await page.locator('#showDebugPanel').getByText('OffOn').click()
@ -82,7 +89,7 @@ test.describe('Testing settings', () => {
await test.step('Open settings with keyboard shortcut', async () => {
await page.getByTestId('settings-close-button').click()
await page.locator('.cm-content').click()
await page.keyboard.press('Meta+Shift+,')
await page.keyboard.press('ControlOrMeta+Shift+,')
await expect(headingLocator).toBeVisible()
})
@ -115,6 +122,36 @@ test.describe('Testing settings', () => {
).not.toBeChecked()
})
test('Keybindings display the correct hotkey for Command Palette', async ({
page,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await test.step('Open keybindings settings', async () => {
// Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('ControlOrMeta+Shift+,')
// Go to Keybindings tab.
const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' })
await keybindingsTab.click()
})
// Go to the hotkey for Command Palette.
const commandPalette = page.getByText('Toggle Command Palette')
await commandPalette.scrollIntoViewIfNeeded()
// The heading is above it and should be in view now.
const commandPaletteHeading = page.getByRole('heading', {
name: 'Command Palette',
})
// The hotkey is in a kbd element next to the heading.
const hotkey = commandPaletteHeading.locator('+ div kbd')
const text = process.platform === 'darwin' ? 'Command+K' : 'Control+K'
await expect(hotkey).toHaveText(text)
})
test('Project and user settings can be reset', async ({ page }) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
@ -203,10 +240,11 @@ test.describe('Testing settings', () => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/bracket/main.kcl`
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
},
})
@ -264,4 +302,69 @@ test.describe('Testing settings', () => {
await electronApp.close()
}
)
test(
`Closing settings modal should go back to the original file being viewed`,
{ tag: '@electron' },
async ({ browser: _ }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async () => {},
})
const {
panesOpen,
createAndSelectProject,
pasteCodeInEditor,
clickPane,
createNewFileAndSelect,
editorTextMatches,
} = await getUtils(page, test)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await panesOpen([])
await test.step('Precondition: No projects exist', async () => {
await expect(page.getByTestId('home-section')).toBeVisible()
const projectLinksPre = page.getByTestId('project-link')
await expect(projectLinksPre).toHaveCount(0)
})
await createAndSelectProject('project-000')
await clickPane('code')
const kclCube = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cube.kcl',
'utf-8'
)
await pasteCodeInEditor(kclCube)
await clickPane('files')
await createNewFileAndSelect('2.kcl')
const kclCylinder = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
'utf-8'
)
await pasteCodeInEditor(kclCylinder)
const settingsOpenButton = page.getByRole('link', {
name: 'settings Settings',
})
const settingsCloseButton = page.getByTestId('settings-close-button')
await test.step('Open and close settings', async () => {
await settingsOpenButton.click()
await settingsCloseButton.click()
})
await test.step('Postcondition: Same file content is in editor as before settings opened', async () => {
await editorTextMatches(kclCylinder)
})
await electronApp.close()
}
)
})

View File

@ -9,8 +9,6 @@ test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
test.describe('Text-to-CAD tests', () => {
test('basic lego happy case', async ({ page }) => {
const u = await getUtils(page)
@ -298,9 +296,9 @@ test.describe('Text-to-CAD tests', () => {
await expect(page.locator('textarea')).toContainText(badPrompt)
// Select all and start a new prompt.
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyA')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
await page.keyboard.type('a 2x4 lego')
// Submit the new prompt.
@ -520,9 +518,9 @@ test.describe('Text-to-CAD tests', () => {
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
// Paste the code.
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyV')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
// Expect the code to be pasted.
await expect(page.locator('.cm-content')).toContainText(`2x8`)
@ -549,13 +547,13 @@ test.describe('Text-to-CAD tests', () => {
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
// Paste the code.
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyA')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
await page.keyboard.press('Backspace')
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyV')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
// Expect the code to be pasted.
await expect(page.locator('.cm-content')).toContainText(`2x4`)
@ -636,9 +634,9 @@ test.describe('Text-to-CAD tests', () => {
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
// Paste the code.
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyV')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
// Expect the code to be pasted.
await expect(page.locator('.cm-content')).toContainText(`2x4`)

View File

@ -1,13 +1,6 @@
import { test, expect } from '@playwright/test'
import {
doExport,
getUtils,
makeTemplate,
metaModifier,
setup,
tearDown,
} from './test-utils'
import { doExport, getUtils, makeTemplate, setup, tearDown } from './test-utils'
test.beforeEach(async ({ context, page }) => {
await setup(context, page)
@ -17,8 +10,6 @@ test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
test('Units menu', async ({ page }) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
@ -157,7 +148,7 @@ test('Paste should not work unless an input is focused', async ({
// Paste without the code pane focused
await codeEditorText.blur()
await page.keyboard.press(`${metaModifier}+KeyV`)
await page.keyboard.press('ControlOrMeta+KeyV')
// Show that the paste didn't work but typing did
await expect(codeEditorText).not.toContainText(pasteContent)
@ -166,7 +157,7 @@ test('Paste should not work unless an input is focused', async ({
// Paste with the code editor focused
// Following this guidance: https://github.com/microsoft/playwright/issues/8114
await codeEditorText.focus()
await page.keyboard.press(`${metaModifier}+KeyV`)
await page.keyboard.press('ControlOrMeta+KeyV')
await expect(
await page.evaluate(
() => document.querySelector('.cm-content')?.textContent
@ -380,9 +371,9 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => {
// Since there's code now, we have to get to the end of the line
await page.locator('.cm-line').last().click()
await page.keyboard.down(CtrlKey)
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('ArrowRight')
await page.keyboard.up(CtrlKey)
await page.keyboard.up('ControlOrMeta')
await page.keyboard.press('Enter')
await page.keyboard.type('//')

View File

@ -6,6 +6,9 @@ import { MakerRpm } from '@electron-forge/maker-rpm'
import { VitePlugin } from '@electron-forge/plugin-vite'
import { FusesPlugin } from '@electron-forge/plugin-fuses'
import { FuseV1Options, FuseVersion } from '@electron/fuses'
import path from 'path'
const rootDir = process.cwd()
const config: ForgeConfig = {
packagerConfig: {
@ -19,13 +22,24 @@ const config: ForgeConfig = {
}) ||
undefined,
executableName: 'zoo-modeling-app',
icon: path.resolve(rootDir, 'assets', 'icon'),
},
rebuildConfig: {},
makers: [
new MakerSquirrel({}),
new MakerSquirrel({
setupIcon: path.resolve(rootDir, 'assets', 'icon.ico'),
}),
new MakerZIP({}, ['darwin']),
new MakerRpm({}),
new MakerDeb({}),
new MakerRpm({
options: {
icon: path.resolve(rootDir, 'assets', 'icon.png'),
},
}),
new MakerDeb({
options: {
icon: path.resolve(rootDir, 'assets', 'icon.png'),
},
}),
],
plugins: [
new VitePlugin({

View File

@ -26,7 +26,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^2.0.0",
"@kittycad/lib": "^2.0.1",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.1",
"@react-hook/resize-observer": "^2.0.1",
@ -83,7 +83,7 @@
"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": "cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && 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",
@ -119,7 +119,7 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.24.3",
"@babel/preset-env": "^7.25.4",
"@electron-forge/cli": "^7.4.0",
"@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/maker-rpm": "^7.4.0",
@ -138,7 +138,7 @@
"@types/electron": "^1.6.10",
"@types/isomorphic-fetch": "^0.0.39",
"@types/mocha": "^10.0.6",
"@types/node": "^22.4.2",
"@types/node": "^22.5.0",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4",
"@types/react": "^18.3.4",
@ -165,7 +165,7 @@
"eslint-plugin-suggest-no-throw": "^1.0.0",
"happy-dom": "^14.3.10",
"http-server": "^14.1.1",
"husky": "^9.0.11",
"husky": "^9.1.5",
"node-fetch": "^3.3.2",
"pixelmatch": "^5.3.0",
"pngjs": "^7.0.0",
@ -176,7 +176,7 @@
"tailwindcss": "^3.4.1",
"ts-node": "^10.0.0",
"typescript": "^5.0.0",
"vite": "^5.0.12",
"vite": "^5.4.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2",

View File

@ -17,6 +17,7 @@ export default defineConfig({
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['dot'],
['list'],
['json', { outputFile: './test-results/report.json' }],
['html'],
],

View File

@ -1,4 +1,4 @@
import { defineConfig } from '@playwright/test'
import { defineConfig, devices } from '@playwright/test'
/**
* See https://playwright.dev/docs/test-configuration.
@ -30,4 +30,24 @@ export default defineConfig({
actionTimeout: 15_000,
screenshot: 'only-on-failure',
},
projects: [
{
name: 'Google Chrome',
use: {
...devices['Desktop Chrome'],
channel: 'chrome',
contextOptions: {
/* Chromium is the only one with these permission types */
permissions: ['clipboard-write', 'clipboard-read'],
},
launchOptions: {
...(process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
? {
executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
}
: {}),
},
}, // or 'chrome-beta'
},
],
})

View File

@ -190,7 +190,7 @@ function CoreDump() {
() => new CoreDumpManager(engineCommandManager, codeManager, token),
[]
)
useHotkeyWrapper(['meta + shift + .'], () => {
useHotkeyWrapper(['mod + shift + .'], () => {
toast.promise(
coreDump(coreDumpManager, true),
{

View File

@ -50,6 +50,7 @@ import {
ExtrudeGroup,
VariableDeclaration,
VariableDeclarator,
sketchGroupFromKclValue,
} from 'lang/wasm'
import {
engineCommandManager,
@ -74,7 +75,13 @@ import {
changeSketchArguments,
updateStartProfileAtArgs,
} from 'lang/std/sketch'
import { isOverlap, normaliseAngle, roundOff, throttle } from 'lib/utils'
import {
isArray,
isOverlap,
normaliseAngle,
roundOff,
throttle,
} from 'lib/utils'
import {
addStartProfileAt,
createArrayExpression,
@ -98,6 +105,7 @@ import {
import { getThemeColorForThreeJs } from 'lib/theme'
import { err, trap } from 'lib/trap'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
type DraftSegment = 'line' | 'tangentialArcTo'
@ -115,6 +123,8 @@ export const SEGMENT_WIDTH_PX = 1.6
export const HIDE_SEGMENT_LENGTH = 75 // in pixels
export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
type Vec3Array = [number, number, number]
// This singleton Class is responsible for all of the things the user sees and interacts with.
// That mostly mean sketch elements.
// Cameras, controls, raycasters, etc are handled by sceneInfra
@ -383,7 +393,7 @@ export class SceneEntities {
if (err(sketchGroup)) return Promise.reject(sketchGroup)
if (!sketchGroup) return Promise.reject('sketchGroup not found')
if (!Array.isArray(sketchGroup?.value))
if (!isArray(sketchGroup?.value))
return {
truncatedAst,
programMemoryOverride,
@ -588,10 +598,12 @@ export class SceneEntities {
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const sg = kclManager.programMemory.get(
const sg = sketchGroupFromKclValue(
kclManager.programMemory.get(variableDeclarationName),
variableDeclarationName
) as SketchGroup
const lastSeg = sg?.value?.slice(-1)[0] || sg.start
)
if (err(sg)) return sg
const lastSeg = sg.value?.slice(-1)[0] || sg.start
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
@ -786,9 +798,11 @@ export class SceneEntities {
programMemoryOverride,
})
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.get(
const sketchGroup = sketchGroupFromKclValue(
programMemory.get(variableDeclarationName),
variableDeclarationName
) as SketchGroup
)
if (err(sketchGroup)) return sketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -840,9 +854,11 @@ export class SceneEntities {
// Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.get(
const sketchGroup = sketchGroupFromKclValue(
programMemory.get(variableDeclarationName),
variableDeclarationName
) as SketchGroup
)
if (err(sketchGroup)) return sketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -1111,8 +1127,12 @@ export class SceneEntities {
const maybeSketchGroup = programMemory.get(variableDeclarationName)
let sketchGroup = undefined
if (maybeSketchGroup?.type === 'SketchGroup') {
sketchGroup = maybeSketchGroup
const sg = sketchGroupFromKclValue(
maybeSketchGroup,
variableDeclarationName
)
if (!err(sg)) {
sketchGroup = sg
} else if ((maybeSketchGroup as ExtrudeGroup).sketchGroup) {
sketchGroup = (maybeSketchGroup as ExtrudeGroup).sketchGroup
}
@ -1656,9 +1676,12 @@ function prepareTruncatedMemoryAndAst(
)
if (err(_node)) return _node
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
const lastSeg = (
programMemory.get(variableDeclarationName) as SketchGroup
).value.slice(-1)[0]
const sg = sketchGroupFromKclValue(
programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sg)) return sg
const lastSeg = sg?.value.slice(-1)[0]
if (draftSegment) {
// truncatedAst needs to setup with another segment at the end
let newSegment
@ -1782,10 +1805,11 @@ export function sketchGroupFromPathToNode({
if (result?.type === 'ExtrudeGroup') {
return result.sketchGroup
}
if (result?.type === 'SketchGroup') {
return result
const sg = sketchGroupFromKclValue(result, varDec?.id?.name)
if (err(sg)) {
return null
}
return null
return sg
}
function colorSegment(object: any, color: number) {
@ -1823,6 +1847,7 @@ export function getSketchQuaternion(
})
if (err(sketchGroup)) return sketchGroup
const zAxis = sketchGroup?.on.zAxis || sketchNormalBackUp
if (!zAxis) return Error('SketchGroup zAxis not found')
return getQuaternionFromZAxis(massageFormats(zAxis))
}
@ -1947,8 +1972,6 @@ export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
return quaternion
}
function massageFormats(a: any): Vector3 {
return Array.isArray(a)
? new Vector3(a[0], a[1], a[2])
: new Vector3(a.x, a.y, a.z)
function massageFormats(a: Vec3Array | Point3d): Vector3 {
return isArray(a) ? new Vector3(a[0], a[1], a[2]) : new Vector3(a.x, a.y, a.z)
}

View File

@ -9,6 +9,8 @@ import useHotkeyWrapper from 'lib/hotkeyWrapper'
import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip'
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
export const CommandBar = () => {
const { pathname } = useLocation()
const { commandBarState, commandBarSend } = useCommandsContext()
@ -24,7 +26,7 @@ export const CommandBar = () => {
}, [pathname])
// Hook up keyboard shortcuts
useHotkeyWrapper(['mod+k'], () => {
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
if (commandBarState.context.commands.length === 0) return
if (commandBarState.matches('Closed')) {
commandBarSend({ type: 'Open' })

View File

@ -1,6 +1,5 @@
import { Completion } from '@codemirror/autocomplete'
import { EditorView, ViewUpdate } from '@codemirror/view'
import { EditorState } from '@codemirror/state'
import { CustomIcon } from 'components/CustomIcon'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
@ -84,17 +83,13 @@ function CommandBarKclInput({
if (event.key === 'Backspace' && value === '') {
event.preventDefault()
stepBack()
} else if (event.key === 'Enter') {
event.preventDefault()
handleSubmit()
}
},
}),
varMentions(varMentionData),
EditorState.transactionFilter.of((tr) => {
if (tr.newDoc.lines > 1) {
handleSubmit()
return []
}
return tr
}),
EditorView.updateListener.of((vu: ViewUpdate) => {
if (vu.docChanged) {
setValue(vu.state.doc.toString())

View File

@ -1,5 +1,7 @@
import { useCommandsContext } from 'hooks/useCommandsContext'
import usePlatform from 'hooks/usePlatform'
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
export function CommandBarOpenButton() {
const { commandBarSend } = useCommandsContext()
@ -12,7 +14,7 @@ export function CommandBarOpenButton() {
>
<span>Commands</span>
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
{platform === 'macos' ? '⌘K' : '^/'}
{hotkeyDisplay(COMMAND_PALETTE_HOTKEY, platform)}
</kbd>
</button>
)

View File

@ -61,6 +61,7 @@ function RenameForm({
<label>
<span className="sr-only">Rename file</span>
<input
data-testid="file-rename-field"
ref={inputRef}
type="text"
autoFocus
@ -395,13 +396,14 @@ export const FileTreeMenu = () => {
})
}
useHotkeyWrapper(['meta + n'], createFile)
useHotkeyWrapper(['meta + shift + n'], createFolder)
useHotkeyWrapper(['mod + n'], createFile)
useHotkeyWrapper(['mod + shift + n'], createFolder)
return (
<>
<ActionButton
Element="button"
data-testid="create-file-button"
iconStart={{
icon: 'filePlus',
iconClassName: '!text-current',
@ -417,6 +419,7 @@ export const FileTreeMenu = () => {
<ActionButton
Element="button"
data-testid="create-folder-button"
iconStart={{
icon: 'folderPlus',
iconClassName: '!text-current',

View File

@ -9,7 +9,7 @@ import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { coreDump } from 'lang/wasm'
import toast from 'react-hot-toast'
import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow'
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
export function LowerRightControls({
@ -66,6 +66,9 @@ export function LowerRightControls({
{children}
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
<a
onClick={openExternalBrowserIfDesktop(
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
target="_blank"
rel="noopener noreferrer"

View File

@ -1,11 +1,16 @@
import toast from 'react-hot-toast'
import ReactJson from 'react-json-view'
import { useMemo } from 'react'
import { ProgramMemory, Path, ExtrudeSurface } from 'lang/wasm'
import {
ProgramMemory,
Path,
ExtrudeSurface,
sketchGroupFromKclValue,
} from 'lang/wasm'
import { useKclContext } from 'lang/KclProvider'
import { useResolvedTheme } from 'hooks/useResolvedTheme'
import { ActionButton } from 'components/ActionButton'
import { trap } from 'lib/trap'
import { err, trap } from 'lib/trap'
import Tooltip from 'components/Tooltip'
import { useModelingContext } from 'hooks/useModelingContext'
@ -84,12 +89,13 @@ export const processMemory = (programMemory: ProgramMemory) => {
const processedMemory: any = {}
for (const [key, val] of programMemory?.visibleEntries()) {
if (typeof val.value !== 'function') {
if (val.type === 'SketchGroup') {
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
const sg = sketchGroupFromKclValue(val, null)
if (val.type === 'ExtrudeGroup') {
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
return rest
})
} else if (val.type === 'ExtrudeGroup') {
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
} else if (!err(sg)) {
processedMemory[key] = sg.value.map(({ __geoMeta, ...rest }: Path) => {
return rest
})
} else if ((val.type as any) === 'Function') {

View File

@ -1,44 +1,11 @@
import { isDesktop } from 'lib/isDesktop'
import { Platform, platform } from 'lib/utils'
import { useEffect, useState } from 'react'
export type Platform = 'macos' | 'windows' | 'linux' | ''
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 ''
}
}
if (isDesktop()) {
setPlatformName(getPlatform())
} else {
if (navigator.userAgent.indexOf('Mac') !== -1) {
setPlatformName('macos')
} else if (navigator.userAgent.indexOf('Win') !== -1) {
setPlatformName('windows')
} else if (navigator.userAgent.indexOf('Linux') !== -1) {
setPlatformName('linux')
}
}
setPlatformName(platform())
}, [setPlatformName])
return platformName

View File

@ -18,41 +18,45 @@ const mySketch001 = startSketchOn('XY')
// @ts-ignore
const sketch001 = programMemory?.get('mySketch001')
expect(sketch001).toEqual({
type: 'SketchGroup',
on: expect.any(Object),
start: {
to: [0, 0],
from: [0, 0],
tag: null,
__geoMeta: {
id: expect.any(String),
sourceRange: [46, 71],
},
},
value: [
{
type: 'ToPoint',
tag: null,
to: [-1.59, -1.54],
from: [0, 0],
__geoMeta: {
sourceRange: [77, 102],
id: expect.any(String),
},
},
{
type: 'ToPoint',
to: [0.46, -5.82],
from: [-1.59, -1.54],
tag: null,
__geoMeta: {
sourceRange: [108, 132],
id: expect.any(String),
},
},
],
id: expect.any(String),
type: 'UserVal',
__meta: [{ sourceRange: [46, 71] }],
value: {
type: 'SketchGroup',
on: expect.any(Object),
start: {
to: [0, 0],
from: [0, 0],
tag: null,
__geoMeta: {
id: expect.any(String),
sourceRange: [46, 71],
},
},
value: [
{
type: 'ToPoint',
tag: null,
to: [-1.59, -1.54],
from: [0, 0],
__geoMeta: {
sourceRange: [77, 102],
id: expect.any(String),
},
},
{
type: 'ToPoint',
to: [0.46, -5.82],
from: [-1.59, -1.54],
tag: null,
__geoMeta: {
sourceRange: [108, 132],
id: expect.any(String),
},
},
],
id: expect.any(String),
__meta: [{ sourceRange: [46, 71] }],
},
})
})
test('extrude artifacts', async () => {

View File

@ -1,6 +1,12 @@
import fs from 'node:fs'
import { parse, ProgramMemory, SketchGroup, initPromise } from './wasm'
import {
parse,
ProgramMemory,
SketchGroup,
initPromise,
sketchGroupFromKclValue,
} from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { KCLError } from './errors'
@ -52,7 +58,7 @@ const newVar = myVar + 1`
`
const mem = await exe(code)
// geo is three js buffer geometry and is very bloated to have in tests
const minusGeo = mem.get('mySketch')?.value
const minusGeo = mem.get('mySketch')?.value?.value
expect(minusGeo).toEqual([
{
type: 'ToPoint',
@ -146,68 +152,72 @@ const newVar = myVar + 1`
].join('\n')
const mem = await exe(code)
expect(mem.get('mySk1')).toEqual({
type: 'SketchGroup',
on: expect.any(Object),
start: {
to: [0, 0],
from: [0, 0],
tag: null,
__geoMeta: {
id: expect.any(String),
sourceRange: [39, 63],
},
},
tags: {
myPath: {
__meta: [
{
sourceRange: [109, 116],
},
],
type: 'TagIdentifier',
value: 'myPath',
info: expect.any(Object),
},
},
value: [
{
type: 'ToPoint',
to: [1, 1],
type: 'UserVal',
value: {
type: 'SketchGroup',
on: expect.any(Object),
start: {
to: [0, 0],
from: [0, 0],
tag: null,
__geoMeta: {
sourceRange: [69, 85],
id: expect.any(String),
sourceRange: [39, 63],
},
},
{
type: 'ToPoint',
to: [0, 1],
from: [1, 1],
__geoMeta: {
sourceRange: [91, 117],
id: expect.any(String),
},
tag: {
end: 116,
start: 109,
type: 'TagDeclarator',
tags: {
myPath: {
__meta: [
{
sourceRange: [109, 116],
},
],
type: 'TagIdentifier',
value: 'myPath',
digest: null,
info: expect.any(Object),
},
},
{
type: 'ToPoint',
to: [1, 1],
from: [0, 1],
tag: null,
__geoMeta: {
sourceRange: [123, 139],
id: expect.any(String),
value: [
{
type: 'ToPoint',
to: [1, 1],
from: [0, 0],
tag: null,
__geoMeta: {
sourceRange: [69, 85],
id: expect.any(String),
},
},
},
],
id: expect.any(String),
{
type: 'ToPoint',
to: [0, 1],
from: [1, 1],
__geoMeta: {
sourceRange: [91, 117],
id: expect.any(String),
},
tag: {
end: 116,
start: 109,
type: 'TagDeclarator',
value: 'myPath',
digest: null,
},
},
{
type: 'ToPoint',
to: [1, 1],
from: [0, 1],
tag: null,
__geoMeta: {
sourceRange: [123, 139],
id: expect.any(String),
},
},
],
id: expect.any(String),
__meta: [{ sourceRange: [39, 63] }],
},
__meta: [{ sourceRange: [39, 63] }],
})
})
@ -358,7 +368,7 @@ describe('testing math operators', () => {
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
].join('\n')
const mem = await exe(code)
const sketch = mem.get('part001')
const sketch = sketchGroupFromKclValue(mem.get('part001'), 'part001')
// result of `-legLen(5, min(3, 999))` should be -4
const yVal = (sketch as SketchGroup).value?.[0]?.to?.[1]
expect(yVal).toBe(-4)
@ -376,7 +386,7 @@ describe('testing math operators', () => {
``,
].join('\n')
const mem = await exe(code)
const sketch = mem.get('part001')
const sketch = sketchGroupFromKclValue(mem.get('part001'), 'part001')
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
@ -385,7 +395,10 @@ describe('testing math operators', () => {
`legLen(segLen(seg01), myVar)`
)
const removedUnaryExpMem = await exe(removedUnaryExp)
const removedUnaryExpMemSketch = removedUnaryExpMem.get('part001')
const removedUnaryExpMemSketch = sketchGroupFromKclValue(
removedUnaryExpMem.get('part001'),
'part001'
)
// without the minus sign, the y value should be 8
expect((removedUnaryExpMemSketch as SketchGroup).value?.[1]?.to).toEqual([

View File

@ -17,7 +17,7 @@ import {
PathToNode,
ProgramMemory,
SourceRange,
SketchGroup,
sketchGroupFromKclValue,
} from './wasm'
import {
isNodeSafeToReplacePath,
@ -563,7 +563,7 @@ export function createArrayExpression(
start: 0,
end: 0,
digest: null,
nonCodeMeta: { nonCodeNodes: {}, start: [], digest: null },
nonCodeMeta: nonCodeMetaEmpty(),
elements,
}
}
@ -577,7 +577,7 @@ export function createPipeExpression(
end: 0,
digest: null,
body,
nonCodeMeta: { nonCodeNodes: {}, start: [], digest: null },
nonCodeMeta: nonCodeMetaEmpty(),
}
}
@ -613,6 +613,7 @@ export function createObjectExpression(properties: {
start: 0,
end: 0,
digest: null,
nonCodeMeta: nonCodeMetaEmpty(),
properties: Object.entries(properties).map(([key, value]) => ({
type: 'ObjectProperty',
start: 0,
@ -981,7 +982,11 @@ export async function deleteFromSelection(
if (err(parent)) {
return
}
const sketchToPreserve = programMemory.get(sketchName) as SketchGroup
const sketchToPreserve = sketchGroupFromKclValue(
programMemory.get(sketchName),
sketchName
)
if (err(sketchToPreserve)) return sketchToPreserve
console.log('sketchName', sketchName)
// Can't kick off multiple requests at once as getFaceDetails
// is three engine calls in one and they conflict
@ -1061,3 +1066,7 @@ export async function deleteFromSelection(
return new Error('Selection not recognised, could not delete')
}
const nonCodeMetaEmpty = () => {
return { nonCodeNodes: {}, start: [], digest: null }
}

View File

@ -10,12 +10,12 @@ import {
Program,
ProgramMemory,
ReturnStatement,
SketchGroup,
SourceRange,
SyntaxType,
Expr,
VariableDeclaration,
VariableDeclarator,
sketchGroupFromKclValue,
} from './wasm'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
@ -661,15 +661,16 @@ export function isLinesParallelAndConstrained(
if (err(_varDec)) return _varDec
const varDec = _varDec.node
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name
const path = programMemory?.get(varName) as SketchGroup
const sg = sketchGroupFromKclValue(programMemory?.get(varName), varName)
if (err(sg)) return sg
const _primarySegment = getSketchSegmentFromSourceRange(
path,
sg,
primaryLine.range
)
if (err(_primarySegment)) return _primarySegment
const primarySegment = _primarySegment.segment
const _segment = getSketchSegmentFromSourceRange(path, secondaryLine.range)
const _segment = getSketchSegmentFromSourceRange(sg, secondaryLine.range)
if (err(_segment)) return _segment
const { segment: secondarySegment, index: secondaryIndex } = _segment
const primaryAngle = getAngle(primarySegment.from, primarySegment.to)
@ -708,9 +709,7 @@ export function isLinesParallelAndConstrained(
constraintType === 'angle' || constraintLevel === 'full'
// get the previous segment
const prevSegment = (programMemory.get(varName) as SketchGroup).value[
secondaryIndex - 1
]
const prevSegment = sg.value[secondaryIndex - 1]
const prevSourceRange = prevSegment.__geoMeta.sourceRange
const isParallelAndConstrained =
@ -779,7 +778,10 @@ export function hasExtrudeSketchGroup({
if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declarations[0].id.name
const varValue = programMemory?.get(varName)
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
return (
varValue?.type === 'ExtrudeGroup' ||
!err(sketchGroupFromKclValue(varValue, varName))
)
}
export function isSingleCursorInPipe(

View File

@ -12,6 +12,7 @@ import {
Literal,
VariableDeclaration,
Identifier,
sketchGroupFromKclValue,
} from 'lang/wasm'
import {
getNodeFromPath,
@ -1009,9 +1010,12 @@ export const angledLineOfXLength: SketchLineHelper = {
const { node: varDec } = nodeMeta2
const variableName = varDec.id.name
const sketch = previousProgramMemory?.get(variableName)
if (!sketch || sketch.type !== 'SketchGroup') {
return new Error('not a SketchGroup')
const sketch = sketchGroupFromKclValue(
previousProgramMemory?.get(variableName),
variableName
)
if (err(sketch)) {
return sketch
}
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
@ -1105,10 +1109,11 @@ export const angledLineOfYLength: SketchLineHelper = {
if (err(nodeMeta2)) return nodeMeta2
const { node: varDec } = nodeMeta2
const variableName = varDec.id.name
const sketch = previousProgramMemory?.get(variableName)
if (!sketch || sketch.type !== 'SketchGroup') {
return new Error('not a SketchGroup')
}
const sketch = sketchGroupFromKclValue(
previousProgramMemory?.get(variableName),
variableName
)
if (err(sketch)) return sketch
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
@ -1443,7 +1448,11 @@ export const angledLineThatIntersects: SketchLineHelper = {
const { node: varDec } = nodeMeta2
const varName = varDec.declarations[0].id.name
const sketchGroup = previousProgramMemory.get(varName) as SketchGroup
const sketchGroup = sketchGroupFromKclValue(
previousProgramMemory.get(varName),
varName
)
if (err(sketchGroup)) return sketchGroup
const intersectPath = sketchGroup.value.find(
({ tag }: Path) => tag && tag.value === intersectTagName
)

View File

@ -1,4 +1,10 @@
import { parse, SketchGroup, recast, initPromise } from '../wasm'
import {
parse,
SketchGroup,
recast,
initPromise,
sketchGroupFromKclValue,
} from '../wasm'
import {
ConstraintType,
getTransformInfos,
@ -362,10 +368,11 @@ const part001 = startSketchOn('XY')
it('normal case works', async () => {
const programMemory = await enginelessExecutor(parse(code))
const index = code.indexOf('// normal-segment') - 7
const _segment = getSketchSegmentFromSourceRange(
programMemory.get('part001') as SketchGroup,
[index, index]
)
const sg = sketchGroupFromKclValue(
programMemory.get('part001'),
'part001'
) as SketchGroup
const _segment = getSketchSegmentFromSourceRange(sg, [index, index])
if (err(_segment)) throw _segment
const { __geoMeta, ...segment } = _segment.segment
expect(segment).toEqual({
@ -379,7 +386,10 @@ const part001 = startSketchOn('XY')
const programMemory = await enginelessExecutor(parse(code))
const index = code.indexOf('// segment-in-start') - 7
const _segment = getSketchSegmentFromSourceRange(
programMemory.get('part001') as SketchGroup,
sketchGroupFromKclValue(
programMemory.get('part001'),
'part001'
) as SketchGroup,
[index, index]
)
if (err(_segment)) throw _segment

View File

@ -10,6 +10,7 @@ import {
VariableDeclarator,
PathToNode,
ProgramMemory,
sketchGroupFromKclValue,
} from '../wasm'
import {
getNodeFromPath,
@ -1636,12 +1637,16 @@ export function transformAstSketchLines({
})
const varName = varDec.node.id.name
let sketchGroup = programMemory.get(varName)
if (sketchGroup?.type === 'ExtrudeGroup') {
sketchGroup = sketchGroup.sketchGroup
let kclVal = programMemory.get(varName)
let sketchGroup
if (kclVal?.type === 'ExtrudeGroup') {
sketchGroup = kclVal.sketchGroup
} else {
sketchGroup = sketchGroupFromKclValue(kclVal, varName)
if (err(sketchGroup)) {
return
}
}
if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
return new Error('not a sketch group')
const segMeta = getSketchSegmentFromPathToNode(
sketchGroup,
ast,

View File

@ -16,7 +16,6 @@ import init, {
parse_app_settings,
parse_project_settings,
default_project_settings,
parse_project_route,
base64_decode,
} from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors'
@ -33,11 +32,11 @@ import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow'
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'
import { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
@ -126,6 +125,7 @@ export const parse = (code: string | Error): Program | Error => {
const program: Program = parse_wasm(code)
return program
} catch (e: any) {
// throw e
const parsed: RustKclError = JSON.parse(e.toString())
return new KCLError(
parsed.kind,
@ -312,7 +312,7 @@ export class ProgramMemory {
*/
hasSketchOrExtrudeGroup(): boolean {
for (const node of this.visibleEntries().values()) {
if (node.type === 'ExtrudeGroup' || node.type === 'SketchGroup') {
if (node.type === 'ExtrudeGroup' || node.value?.type === 'SketchGroup') {
return true
}
}
@ -332,6 +332,28 @@ export class ProgramMemory {
}
}
// TODO: In the future, make the parameter be a KclValue.
export function sketchGroupFromKclValue(
obj: any,
varName: string | null
): SketchGroup | Error {
if (obj?.value?.type === 'SketchGroup') return obj.value
if (obj?.value?.type === 'ExtrudeGroup') return obj.value.sketchGroup
if (obj?.type === 'ExtrudeGroup') return obj.sketchGroup
if (!varName) {
varName = 'a KCL value'
}
const actualType = obj?.value?.type ?? obj?.type
if (actualType) {
console.log(obj)
return new Error(
`Expected ${varName} to be a sketchGroup or extrudeGroup, but it was ${actualType} instead.`
)
} else {
return new Error(`Expected ${varName} to be a sketchGroup, but it wasn't.`)
}
}
export const executor = async (
node: Program,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
@ -590,13 +612,6 @@ export function parseProjectSettings(
return parse_project_settings(toml)
}
export function parseProjectRoute(
configuration: DeepPartial<Configuration>,
route_str: string
): ProjectRoute | Error {
return parse_project_route(JSON.stringify(configuration), route_str)
}
export function base64Decode(base64: string): ArrayBuffer | Error {
try {
const decoded = base64_decode(base64)

View File

@ -109,11 +109,13 @@ export class CoreDumpManager {
getWebrtcStats(): Promise<string> {
if (!this.engineCommandManager.engineConnection) {
throw new Error('Engine connection not initialized')
// when the engine connection is not available, return an empty object.
return Promise.resolve(JSON.stringify({}))
}
if (!this.engineCommandManager.engineConnection.webrtcStatsCollector) {
throw new Error('Engine webrtcStatsCollector not initialized')
// when the engine connection is not available, return an empty object.
return Promise.resolve(JSON.stringify({}))
}
return this.engineCommandManager.engineConnection

View File

@ -19,7 +19,6 @@ import {
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(
projectPath: string,
@ -39,7 +38,7 @@ export async function renameProjectDirectory(
// Make sure the new name does not exist.
const newPath = window.electron.path.join(
projectPath.split('/').slice(0, -1).join('/'),
window.electron.path.dirname(projectPath),
newName
)
try {
@ -186,9 +185,9 @@ 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 name = window.electron.path.basename(path)
let entry: FileEntry = {
name: pathParts.slice(-1)[0],
name: name,
path,
children: [],
}
@ -330,7 +329,6 @@ export async function getProjectInfo(projectPath: string): Promise<Project> {
new Error(`Project path is not a directory: ${projectPath}`)
)
}
let walked = await collectAllFilesRecursiveFrom(projectPath)
let default_file = await getDefaultKclFileForDir(projectPath, walked)
const metadata = await window.electron.stat(projectPath)
@ -443,28 +441,34 @@ export const readProjectSettingsFile = async (
return configObj
}
/**
* Read the app settings file, or creates an initial one if it doesn't exist.
*/
export const readAppSettingsFile = async () => {
let settingsPath = await getAppSettingsFilePath()
try {
await window.electron.stat(settingsPath)
} catch (e) {
if (e === 'ENOENT') {
const config = defaultAppSettings()
if (err(config)) return Promise.reject(config)
if (!config.settings?.app)
return Promise.reject(new Error('config.app is falsey'))
config.settings.app.project_directory = await getInitialDefaultDir()
return config
// The file exists, read it and parse it.
if (window.electron.exists(settingsPath)) {
const configToml = await window.electron.readFile(settingsPath)
const configObj = parseAppSettings(configToml)
if (err(configObj)) {
return Promise.reject(configObj)
}
}
const configToml = await window.electron.readFile(settingsPath)
const configObj = parseAppSettings(configToml)
if (err(configObj)) {
return Promise.reject(configObj)
return configObj
}
return configObj
// The file doesn't exist, create a new one.
// This defaultAppConfig is truly an empty object every time.
const defaultAppConfig = defaultAppSettings()
if (err(defaultAppConfig)) {
return Promise.reject(defaultAppConfig)
}
const initialDirConfig: DeepPartial<Configuration> = {
settings: { project: { directory: await getInitialDefaultDir() } },
}
const config = Object.assign(defaultAppConfig, initialDirConfig)
return config
}
export const writeAppSettingsFile = async (tomlStr: string) => {

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