Compare commits

...

75 Commits

Author SHA1 Message Date
7329753211 Bump kcl versions (#6089) 2025-04-01 12:43:27 -04:00
7d46e7e271 fix api for edge cuts (#6086)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-01 12:42:11 -04:00
0583eb07d5 Remove unused test code to fix console error (#6073) 2025-04-01 03:08:04 +00:00
73694563cf change TyF64 to f64 according to JsonSchema and cleanup docs code (#6081)
* cleanup gen_std

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

* cleanup docs

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

* updates

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

* fix table

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-01 01:02:48 +00:00
bb4ed59191 Fix patterning in module to not fail when importing function from the module (#6082)
* Add failing test

* Update output to show execution error

* Fix circular pattern to not error in isolated or mock mode

* Update output after fix

* Add failing test for pattern linear 2D

* Add failing output

* Fix isolated execution in linear patterns

* Update output after linear fix
2025-03-31 23:46:29 +00:00
566143757f Fix 404 when clicking on the car wheel sample (#6079)
Fixes #6078
2025-03-31 23:09:59 +00:00
822f2ffc73 Add edit flow for Revolve point-and-click (#5951)
* WIP: Add edge and segment selection in point-and-click Helix flow
Fixes #5393

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Working edge based helix edit

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Add utility function for shared code between revolve and helix

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Use updateModelingState in codemod

* A snapshot a day keeps the bugs away! 📷🐛

* Add skip: true for edge helix to be consistent with axis as options

* A snapshot a day keeps the bugs away! 📷🐛

* Add support for sweepEdge and tests

* Lint

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Clean up snapshots

* WIP: Add edit flow for Revolve
Fixes #5504

* A snapshot a day keeps the bugs away! 📷🐛

* Clean up, add edit steps to three e2e tests

* Fix up tests after ccw change

* Use displayName: 'CounterClockWise' cause ccw not cutting it

* Fix tsc

* Remove uneeded return

* Update 2020 snapshots after helix change

* Update 2020 snapshots after helix change

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Another one :djkhaled:

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Move to fromPromise actor, fix test

* A snapshot a day keeps the bugs away! 📷🐛

* Clean up

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Lint

* Fix third test

* A snapshot a day keeps the bugs away! 📷🐛

* Lint

* Fix edit insert order

* Fix axis, edge cases with new var, edit new var

* Add test case for new variable creation

* Clean up addRevolve with getSafeInsertIndex

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-31 22:13:34 +00:00
eb3ceba497 Minor design tweaks to log in page (#6071)
Bump subheadings, make link buttons primary colored
2025-03-31 15:33:27 -04:00
2c6d0621c9 Add test for #4236: "Fix corner rect panning bug" (#6018)
* add test for rect panning bug

* tweak timeouts

* cleanups, handle both center and corner rectangle

* revert regression that was used for the test

* apply PR feedback
2025-03-31 15:31:22 -04:00
d8e84cb5e3 Change default tolerance value to not depend on units (#6055) 2025-03-31 19:28:15 +00:00
0b1e79871f Bump the patch group across 1 directory with 16 updates (#6068)
Bumps the patch group with 16 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.8.0` | `6.8.1` |
| [@codemirror/lint](https://github.com/codemirror/lint) | `6.8.4` | `6.8.5` |
| [@codemirror/state](https://github.com/codemirror/state) | `6.5.0` | `6.5.2` |
| [@csstools/postcss-oklab-function](https://github.com/csstools/postcss-plugins/tree/HEAD/plugins/postcss-oklab-function) | `4.0.7` | `4.0.8` |
| [@headlessui/tailwindcss](https://github.com/tailwindlabs/headlessui/tree/HEAD/packages/@headlessui-tailwindcss) | `0.2.1` | `0.2.2` |
| [@kittycad/lib](https://github.com/KittyCAD/kittycad.ts) | `2.0.21` | `2.0.23` |
| [chokidar](https://github.com/paulmillr/chokidar) | `4.0.1` | `4.0.3` |
| [electron-updater](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-updater) | `6.6.0` | `6.6.2` |
| [@playwright/test](https://github.com/microsoft/playwright) | `1.51.0` | `1.51.1` |
| [@types/diff](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/diff) | `7.0.1` | `7.0.2` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.13.9` | `22.13.14` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `4.3.1` | `4.3.4` |
| [autoprefixer](https://github.com/postcss/autoprefixer) | `10.4.19` | `10.4.21` |
| [electron-builder](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-builder) | `26.0.7` | `26.0.12` |
| [ws](https://github.com/websockets/ws) | `8.18.0` | `8.18.1` |
| [@types/ws](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ws) | `8.5.13` | `8.18.0` |



Updates `@codemirror/commands` from 6.8.0 to 6.8.1
- [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/commands/compare/6.8.0...6.8.1)

Updates `@codemirror/lint` from 6.8.4 to 6.8.5
- [Changelog](https://github.com/codemirror/lint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/lint/compare/6.8.4...6.8.5)

Updates `@codemirror/state` from 6.5.0 to 6.5.2
- [Changelog](https://github.com/codemirror/state/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/state/compare/6.5.0...6.5.2)

Updates `@csstools/postcss-oklab-function` from 4.0.7 to 4.0.8
- [Changelog](https://github.com/csstools/postcss-plugins/blob/main/plugins/postcss-oklab-function/CHANGELOG.md)
- [Commits](https://github.com/csstools/postcss-plugins/commits/HEAD/plugins/postcss-oklab-function)

Updates `@headlessui/tailwindcss` from 0.2.1 to 0.2.2
- [Release notes](https://github.com/tailwindlabs/headlessui/releases)
- [Changelog](https://github.com/tailwindlabs/headlessui/blob/main/packages/@headlessui-tailwindcss/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/headlessui/commits/@headlessui/tailwindcss@v0.2.2/packages/@headlessui-tailwindcss)

Updates `@kittycad/lib` from 2.0.21 to 2.0.23
- [Release notes](https://github.com/KittyCAD/kittycad.ts/releases)
- [Commits](https://github.com/KittyCAD/kittycad.ts/compare/v2.0.21...v2.0.23)

Updates `chokidar` from 4.0.1 to 4.0.3
- [Release notes](https://github.com/paulmillr/chokidar/releases)
- [Commits](https://github.com/paulmillr/chokidar/compare/4.0.1...4.0.3)

Updates `electron-updater` from 6.6.0 to 6.6.2
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/electron-updater@6.6.2/packages/electron-updater)

Updates `@playwright/test` from 1.51.0 to 1.51.1
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.51.0...v1.51.1)

Updates `@types/diff` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/diff)

Updates `@types/node` from 22.13.9 to 22.13.14
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@vitejs/plugin-react` from 4.3.1 to 4.3.4
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.4/packages/plugin-react)

Updates `autoprefixer` from 10.4.19 to 10.4.21
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/autoprefixer/compare/10.4.19...10.4.21)

Updates `electron-builder` from 26.0.7 to 26.0.12
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-builder/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/v26.0.12/packages/electron-builder)

Updates `ws` from 8.18.0 to 8.18.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.0...8.18.1)

Updates `@types/ws` from 8.5.13 to 8.18.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/ws)

---
updated-dependencies:
- dependency-name: "@codemirror/commands"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@codemirror/lint"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@codemirror/state"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@csstools/postcss-oklab-function"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@headlessui/tailwindcss"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@kittycad/lib"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: chokidar
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: electron-updater
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@playwright/test"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@types/diff"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@vitejs/plugin-react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: autoprefixer
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: electron-builder
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: ws
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: "@types/ws"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 18:56:37 +00:00
954fddf578 Show a toast if the user tries to save their work (#6051)
* Show a toast if the user tries to save their work

And store the fact that they've seen it in localStorage so we don't spam
them every time they hit it after they see it.

* Remove any localStorage logic, toast every time
2025-03-31 14:37:04 -04:00
f94f748339 Use waitForExecutionDone in lint errors test (#6070) 2025-03-31 13:43:43 -04:00
e2b7b22ca9 ctx.close in tests when error on execution (#6075)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-31 10:24:42 -07:00
efc8c82d8b BREAKING: KCL @settings are the source of truth for units (#5808) 2025-03-31 10:56:03 -04:00
eac5abba79 Clean up env vars in e2e-tests.yml (#6045)
* pierremtb/adhoc/clean-up-e2e-env-vars-ci

* Add back           VITE_KC_SKIP_AUTH: true for snaps

* Remove VITE_KC_SKIP_AUTH: true, add back token
2025-03-31 09:18:23 -04:00
1956c14b8a auto format kcl samples (#6065)
* recast kcl samples;

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

* auto format the kcl-samples

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-30 17:06:36 +00:00
017fac7041 use deterministic ids in more places (#6064)
* dont redact the ids now that they are deterministic

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

pass arouund id generator more

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

change the anme space

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

updates and re-run

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

updates

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

* fixups

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

* fixes

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

* cleanup old files

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

* cleanup old files

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-30 04:23:11 +00:00
0bdc50c78f bump kcl-lib and friends (#6063)
* bump kcl-lib and friends

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

* expose

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

* relevant files

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

* udpates

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

* fixes

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-29 19:26:20 -07:00
57d78b6094 Move artifact graph out of engine connection (#6062)
* cleanups

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

* cleanups

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

* fmt

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-29 20:25:26 -04:00
db5ce7ba85 Move turns to a submodule of std (#6039)
* Move turns to a submodule of std

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Cache module infos as well as memory; fix a bug with deprecated constants

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-03-30 11:10:44 +13:00
51c16d0048 only rust changes reset scene (#6060)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-29 11:43:42 -07:00
6532b23f1c Bump tar-fs from 2.1.1 to 2.1.2 in /rust/kcl-language-server in the security group (#6056) 2025-03-29 16:35:45 +00:00
49304b9ecd fix context closes (#6058)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-28 20:31:44 -07:00
358b34de4c Fix translate scale & better docs (#6053)
* change translate & scale to be better & docs

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

* autocomplete

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

* gen std

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

* kcl-samples

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-28 21:14:29 +00:00
9973e5fde3 Fix to preserve comments when changing file units (#6052) 2025-03-28 21:07:53 +00:00
36875e05fd Fix .length on undefined WASM error (#6048) 2025-03-28 14:48:47 -04:00
42123383bb Add path argument error to console error whitelist (#6046)
* Add path argument error to console error whitelist

* Lint
2025-03-28 13:50:54 -04:00
ef4c606ed1 Update sweep-related icons to be less detailed, show result (#6047)
* WIP trying out various icons

* Update to use outcome bodies for icons
2025-03-28 16:42:31 +00:00
1ebb73b935 Fix 'originalCode' undefined in a test (#6027) 2025-03-28 12:36:54 -04:00
b57d31c0e7 Fix build:wasm:dev for --dev builds (#6038)
This is useful since it preserves Rust backtraces in WASM.
2025-03-28 08:40:21 -04:00
678ebbc310 Make Helix available in numbered releases (#6024)
* Helix release outside of dev and nightly

* Make length non required on edge mode so we get the edge length by default
2025-03-28 11:25:32 +00:00
cc2efd316c Add more TS lints and fix types (#6037)
* Add as const lint

* Add lint for no implied eval

* Fix incorrect type and add lints

* Add more type lints

* Remove redundant type assertions and add lint

* Fix to turn off incorrect base rules

* Fix yarn lint workflow to wait for build:wasm

* Change so that we don't build:wasm more than once in the workflow
2025-03-28 04:24:24 +00:00
d1f811f91d Boolean create UI (#5906)
* first steps, add to cmd bar etc

* cmdbar working well enough

* mvp

* lint

* fix after rebase

* intersect and union mvps

* add test

* some clean up

* further fix up

* Update src/lang/modifyAst/boolean.ts

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* Update src/lang/modifyAst/boolean.ts

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* pierre's comments

* tsc

* add comment

---------

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-03-28 03:56:48 +00:00
7ca3afff9f Add CSG operations to the Feature Tree (#6028)
* Add operation tracking for CSG boolean functions

* Add CSG operations to the Feature Tree

* Add just command

* Add union sim test

* Update output with new sim test

* Add CSG subtract test

* Update output from subtract test

* Add intersect sim test

* Update output for intersect test
2025-03-28 09:48:55 +11:00
71b9e40bd9 Fix to not use cursed empty object type and add lint (#6033) 2025-03-27 22:08:57 +00:00
4f35197a96 Add a loading state to CommandBarKclInput while calculating (#6025)
* Add a loading state to CommandBarKclInput while calculating

This is so that we can more reliably await for the calculation to be
completed before advancing the command bar in Playwright E2E tests.

* Make sure the spinner shows on first frame, before `isExecuting` is set too
2025-03-27 16:22:04 -04:00
40b0cf5fd3 Create a new commit to preserve history from main (#6019) 2025-03-27 15:49:38 +00:00
355e6acf0d Make tests fail when there are console errors (#6015)
* Add a test to confirm console errors fail tests

* Check for console errors on all browsers

* Ignore error impacting lots of tests

* Add more detected errors to the allowlist for now
2025-03-27 15:41:25 +00:00
4ff38e7f44 Add dual-sink and makeup mirror to KCL samples (#6023)
* add makeup mirror

* m -> M

* add metal sink unit

* Update kcl-samples simulation test output

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-27 10:57:01 -04:00
1dcd3b84b7 Populate all environment variables from files (#6014) 2025-03-27 10:38:24 -04:00
2957216bd3 Fix kitt kcl-sample expected output (#6022) 2025-03-27 14:31:12 +00:00
11160f0b40 Point-and-click Helix from cylinders (#5979) 2025-03-26 17:57:30 -04:00
4b2c745db5 Fix error: unknown sketch function circle (#6012) 2025-03-26 14:04:59 -04:00
bb983021b1 Feature: Traditional menu actions in desktop application (#5892)
* chore: skeleton for building and creating menus. Need electron to renderer interface to dynamically set the menu

* chore: skeleton typing for communication between nodes and web side

* chore: more skeleton for the different roles within the menu options, need more type safety

* chore: adding more skeleton and templates of what the menus could be

* chore: implemented first pass for helpRole links

* fix: syntax issue stopped the build step

* feature: loading different menus based on your page

* feature: Home page file role implemented

* chore: handling the build workflow for the signin page

* fix: moving edit actionst to the edit menu

* chore: adding preferences to the file role

* chore: redoing help roles based on the question mark widget

* fix: auto fmt

* chore: examples of accelerator strings for Menu.MenuItems keyboard shortcuts!

* chore: oddly specific toggle API for disabling MenuItems from JS. No rules!

* fix: do not implement a custom label disable thingy, use id on menu and use the native APIga

* fix: auto fmt

* fix: adding some typechecks and auto fmt fixes

* fix: trying to fix custom type?

* fix: nvm we back, the lsp on my editor borked for a second

* fix: adding one more level to the custom type for the labels

* chore: cleaning up type definitions to read easier

* fix: resolving yarn lint errors

* chore: adding file sign out

* chore: adding more file bar actions

* chore: ready for PR draft

* fix: preemptive GC collectoin bug fix if somehow a user interacts with a menu while it is being GCed

* fix: linking the OG source

* fix: set application menu to null to avoid default electron menu

* chore: trying to add more typescript

* chore: BIG workflow changes... better typing, less IPC junk

* fix: remapping the icp functions to the cb option select...

* chore: all og events are rehooked up with new workflow pattern

* feat: adding more options to the native bar!

* fix: todo

* chore: cleaning up some menus and adding more

* fix: desktop vs browser and lint errors

* fix: typescript did not like sample electorn JS code for the basic templates with isMac conditionals...

* fix: PR clean up

* fix: more PR cleanup

* A snapshot a day keeps the bugs away! 📷🐛

* fix: added the new help menu to the default sign in and modeling page

* fix: disabled two menu actions within sign in page since they will not do anything.

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* fix: mergining main, auto fixes

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* fix: fixed ipc renderer off/remove listener bug

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* fix: report a bug to refresha and report a bug

* fix: new type for webContents send payload that does not brick TS

* fix: removing import file from url since it is not working in the command palette for manual user input

* fix: removing old comment

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* chore: adding some E2E tests.

* chore: added E2E tests for each file menu

* fix: auto fixes

* chore: adding more edit role E2E tests

* chore: e2e test

* chore: adding help role e2e test

* A snapshot a day keeps the bugs away! 📷🐛

* chore: e2e test for all the menu options you can interact with in the frontend

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 13:03:44 -05:00
d27b8871bc Set bot user info to amend commits (#6011) 2025-03-26 17:12:20 +00:00
1753047d87 Feature: Release named views to all users (#5814)
* chore: cleanup to get named views released!

* fix: fixed gizmo, client side camera sync and remove DEV flag

* yarp

* chore: implementing E2E tests for creating a named view

* fix: cleaning up and commenting E2E tests for named views

* fix: we did it bois, the skip ceral i zation bricked my E2E test :(

* fix: auto formatter

* fix: snapshot uuid matching because rust will randomly generate thme

* fix: auto fmt

* fix: trying to resolve typescript issues

* fix: handling NamedView vs CameraViewState type checking

* fix: no idea I just mapped export to 3d export because we have no 2d export yet...

* fix: random file I wrote because my editor was too slow

* fix: git merge did not do what I wanted

* A snapshot a day keeps the bugs away! 📷🐛

* fix: linter errors

* A snapshot a day keeps the bugs away! 📷🐛

---------

Co-authored-by: 49fl <ircsurfer33@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
2025-03-26 16:12:35 +00:00
fa16fcedff update all kcl-samples w/ format (#5999) 2025-03-26 11:53:34 -04:00
0677474097 Bump KCL in prep for release (#6010) 2025-03-26 15:52:32 +00:00
0c4826cdd5 Prevent overwriting the commit status on main (#6009) 2025-03-26 10:41:41 -04:00
b6fe660b84 Add edit flow for point-and-click Chamfer and Fillet (#5946)
* WIP: Add edit flow for Fillet
Fixes #5521

* Support sweepedge fillet and add edit tests

* A snapshot a day keeps the bugs away! 📷🐛

* Lint and cleanup

* Add edit flow for Chamfer
Fixes #5950

* Change to shared prepareToEdit function

* Clean up

* Lint

* Clean up of types and use of getEdgeCutConsumedCodeRef

* Find pipeIndex instead of hardcode

* Add error for non-pipe fillet and test it

* A snapshot a day keeps the bugs away! 📷🐛

* Fix lint

* A snapshot a day keeps the bugs away! 📷🐛

* Utility function to reduce code reuse across fillet and chamfer

* Clean up test diff

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Lint

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Remove change not needed

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Fix typo in toast

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Remove ['segment', 'sweepEdge'] const as it was causing some sort of circ dep

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 11:57:08 +00:00
c53fa421ad Give a warning when using per-project default units (#5956)
* Give a warning when using per-project default units

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Factor non-settings out of MetaSettings

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fix formatting

* Fix code pane e2e test

* Fix callstack blowup in edit flow

* Avoid dumb timeout issue with command registration in test

* Use a safer way to wait for modeling command registration in test

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
2025-03-26 18:59:43 +13:00
736533a482 Ignore unit tests when running Playwright Electron (#5996)
* Ignore unit tests when running Playwright Electron

* Skip a couple more Windows tests for now
2025-03-25 20:59:07 -04:00
a15565682d Update main.kcl (#5989)
* Update main.kcl

Improving parameter naming in I beam

* Update kcl-samples simulation test output

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Josh Gomez <114548659+jgomez720@users.noreply.github.com>
2025-03-25 23:36:53 +00:00
58861cd24a ci: Fix e2e workflow to always use condition (#5994)
Fix e2e workflow to always use condition
2025-03-25 23:18:57 +00:00
c0cdcb1b98 Disable automated snapshot commits (#6002) 2025-03-25 22:27:30 +00:00
41f45afb3c [bug] Fix edit parameter flow and test (#5997)
* Fix callstack blowup in edit flow

* Avoid dumb timeout issue with command registration in test

* A snapshot a day keeps the bugs away! 📷🐛

* Use a safer way to wait for modeling command registration in test

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* De-flake create parameter test step

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-25 22:10:52 +00:00
e4edffa569 bump kcl friends (#5993)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-25 11:05:17 -07:00
c28bad267a Add utilities to check platform to determine if a test should run (#5983)
* Add tests for utility to bypass unreliable tests

* Limit skip to just Windows

* Ignore unit tests from Playwright

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-25 13:24:41 -04:00
81f92bc7f9 Codify dependencies to run web end-to-end tests (#5926) 2025-03-25 10:35:49 -04:00
58dc3382eb Fix to update the code in the editor before executing (#5976)
* Fix spelling

* Fix to update the code in the editor before executing
2025-03-25 05:06:27 -04:00
f7f7d9fa2c Remove all tooltip delays throughout the app (#5969) 2025-03-24 22:58:54 -04:00
8c3408fda3 Fix: Ignore KCL changes when loading settings and fixed useEffect to use .current (#5126)
* fix: fixed use effect logic to use .current, fixed useEffect file watcher to not watch .kcl files when loading settings

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* bump

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* bump

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* bump

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* fix: merge wrote this back...

* fix: deleted unused variable init

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* bump

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
2025-03-24 23:23:12 +00:00
7c9f1248d4 Extract extrude to variables and format onboarding code (#5949)
* Extract extrude to variables and format onboarding code

* A snapshot a day keeps the bugs away! 📷🐛

* add spaces between sketches and extrudes

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: jgomez720 <114548659+jgomez720@users.noreply.github.com>
2025-03-24 17:10:41 -04:00
89dd4fb039 Add edge and segment selection in point-and-click Helix flow (#5866)
* WIP: Add edge and segment selection in point-and-click Helix flow
Fixes #5393

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Working edge based helix edit

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Add utility function for shared code between revolve and helix

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Use updateModelingState in codemod

* A snapshot a day keeps the bugs away! 📷🐛

* Add skip: true for edge helix to be consistent with axis as options

* A snapshot a day keeps the bugs away! 📷🐛

* Add support for sweepEdge and tests

* Lint

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* Clean up snapshots

* Fix up tests after ccw change

* Use displayName: 'CounterClockWise' cause ccw not cutting it

* Fix tsc

* Update 2020 snapshots after helix change

* Update 2020 snapshots after helix change

* A snapshot a day keeps the bugs away! 📷🐛

* Another one :djkhaled:

* Clean up

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-24 16:08:19 -04:00
fdeb2b3f49 Feature: Implement read write access checking on Project Directory and report any issues in home page (#5676)
* chore: skeleton to detect read write directories and if we have access to notify user

* chore: adding buttont to easily change project directory

* chore: cleaning up home page error bar layout and button

* fix: adding clearer comments

* fix: ugly console debugging but I need to save off progress

* fix: removing project dir check on empty string

* fix: debug progress to save off listProjects once. Still bugged...

* fix: more hard coded debugging to get project loading optimizted

* fix: yarp, we got another one bois

* fix: cleaning up code

* fix: massive bug comment to warn devs about chokidar bugs

* fix: returning error instead of throwing

* fix: cleaning up PR

* fix: fixed loading the projects when the project directory changes

* fix: remove testing code

* fix: only skip directories if you can access the project directory since we don't need to view them

* fix: unit tests, turning off noisey localhost vitest garbage

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* fix: deleted testing state

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
2025-03-24 19:57:01 +00:00
65c455ae7c Use app token to sync 'all-e2e' branch (#5968) 2025-03-24 19:17:04 +00:00
c3e12e5ff7 Update 2020 snapshots after helix change (#5970)
* Update 2020 snapshots after helix change

* Another one :djkhaled:

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-24 14:28:01 -04:00
4dce1612c1 Bump the major group in /rust/kcl-language-server with 4 updates (#5966)
* Bump the major group in /rust/kcl-language-server with 4 updates

Bumps the major group in /rust/kcl-language-server with 4 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser), [@vscode/vsce](https://github.com/Microsoft/vsce) and [glob](https://github.com/isaacs/node-glob).


Updates `@typescript-eslint/eslint-plugin` from 6.21.0 to 8.27.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.27.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 6.21.0 to 8.27.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.27.0/packages/parser)

Updates `@vscode/vsce` from 2.30.0 to 3.3.0
- [Release notes](https://github.com/Microsoft/vsce/releases)
- [Commits](https://github.com/Microsoft/vsce/compare/v2.30.0...v3.3.0)

Updates `glob` from 10.4.5 to 11.0.1
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v11.0.1)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major
- dependency-name: "@vscode/vsce"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major
- dependency-name: glob
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major
...

Signed-off-by: dependabot[bot] <support@github.com>

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-24 10:09:04 -07:00
1b98897120 Bump @types/node from 22.13.10 to 22.13.13 in /packages/codemirror-lsp-client in the patch group (#5962)
Bump @types/node in /packages/codemirror-lsp-client in the patch group

Bumps the patch group in /packages/codemirror-lsp-client with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 22.13.10 to 22.13.13
- [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-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 09:03:46 -04:00
3b2abe5814 Declare std kwarg functions in KCL and migrate circle (#5955)
* Support calling KCL std KW fns, and move circle to KCL std

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Doc comments on parameters

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Update grammar

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Change use of counterClockWise to ccw

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-03-24 21:55:24 +13:00
dddcd5ff46 Support paths to names rather than just raw idents (#5778)
* Support paths to names rather than just raw idents

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

* A snapshot a day keeps the bugs away! 📷🐛

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-24 20:58:55 +13:00
cfbb03765e Add fixme to a file-tree test (#5941) 2025-03-22 07:25:39 -04:00
0d0dd1019b Enable tests to run on pushes to 'all-e2e' branch (#5945)
* Enable tests to run on pushes to 'all-e2e' branch

* Document branch behavior
2025-03-22 01:55:54 +00:00
de2b1b3bea Fix so that only comments doesn't format to empty (#5944) 2025-03-22 00:56:04 +00:00
896 changed files with 178239 additions and 57064 deletions

View File

@ -1,5 +1,6 @@
NODE_ENV=development NODE_ENV=development
DEV=true DEV=true
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
@ -8,3 +9,5 @@ VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000 VITE_KC_CONNECTION_TIMEOUT_MS=5000
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence! # ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
#VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local" #VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
FAIL_ON_CONSOLE_ERRORS=true

12
.envrc
View File

@ -1 +1,13 @@
# Load optional shared environment variables
source_up_if_exists
# Load default development environment variables
dotenv .env.development
# Load optional environment variables overrides
dotenv_if_exists .env.development.local
# Load optional testing environment variables
dotenv_if_exists e2e/playwright/playwright-secrets.env
use flake . use flake .

View File

@ -20,8 +20,20 @@
"plugin:react-hooks/recommended" "plugin:react-hooks/recommended"
], ],
"rules": { "rules": {
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-array-delete": "error",
"@typescript-eslint/no-duplicate-enum-values": "error",
"@typescript-eslint/no-duplicate-type-constituents": "error",
"@typescript-eslint/no-empty-object-type": "error",
"@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-floating-promises": "error",
"no-implied-eval": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-implied-eval": "error",
"@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unnecessary-type-constraint": "error",
"no-unused-vars": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-unused-vars": ["error", { "@typescript-eslint/no-unused-vars": ["error", {
"varsIgnorePattern": "^_", "varsIgnorePattern": "^_",
"argsIgnorePattern": "^_", "argsIgnorePattern": "^_",
@ -29,6 +41,7 @@
"vars": "all", "vars": "all",
"args": "none" "args": "none"
}], }],
"@typescript-eslint/prefer-as-const": "warn",
"jsx-a11y/click-events-have-key-events": "off", "jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off", "jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off", "jsx-a11y/no-noninteractive-element-interactions": "off",

View File

@ -1,7 +1,9 @@
name: E2E Tests name: E2E Tests
on: on:
push: push:
branches: [ main ] branches:
- main
- all-e2e # this bypasses `fixme()` using `orRunWhenFullSuiteEnabled()`
pull_request: pull_request:
schedule: schedule:
- cron: 0 * * * * # hourly - cron: 0 * * * * # hourly
@ -15,7 +17,6 @@ permissions:
pull-requests: write pull-requests: write
actions: read actions: read
jobs: jobs:
conditions: conditions:
@ -67,14 +68,16 @@ jobs:
- name: Display conditions - name: Display conditions
shell: bash shell: bash
run: | run: |
# For debugging purposes. # For debugging purposes
set -euo pipefail set -euo pipefail
echo "GITHUB_REF: $GITHUB_REF"
echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF"
echo "GITHUB_BASE_REF: $GITHUB_BASE_REF"
echo "significant: ${{ steps.path-changes.outputs.significant }}" echo "significant: ${{ steps.path-changes.outputs.significant }}"
echo "should-run: ${{ steps.should-run.outputs.should-run }}" echo "should-run: ${{ steps.should-run.outputs.should-run }}"
prepare-wasm: prepare-wasm:
# seperate job on Ubuntu to build or fetch the wasm blob once on the fastest runner # separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64 runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: conditions needs: conditions
steps: steps:
@ -160,7 +163,6 @@ jobs:
path: | path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib* rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
snapshots: snapshots:
name: playwright:snapshots:ubuntu name: playwright:snapshots:ubuntu
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64 runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
@ -227,10 +229,6 @@ jobs:
timeout_minutes: 30 timeout_minutes: 30
max_attempts: 3 max_attempts: 3
env: env:
CI: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }} snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
@ -243,7 +241,7 @@ jobs:
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
- name: check for changes - name: Check for changes
if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }} if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }}
shell: bash shell: bash
id: git-check id: git-check
@ -255,7 +253,8 @@ jobs:
fi fi
- name: Commit changes, if any - name: Commit changes, if any
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }} # TODO: find a more reliable way to detect visual changes
if: ${{ false && needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
shell: bash shell: bash
run: | run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
@ -341,7 +340,7 @@ jobs:
run: yarn tronb:vite:dev run: yarn tronb:vite:dev
- name: Install vector - name: Install vector
if: contains(matrix.os, 'ubuntu') if: ${{ needs.conditions.outputs.should-run == 'true' && contains(matrix.os, 'ubuntu') }}
shell: bash shell: bash
run: | run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
@ -374,11 +373,7 @@ jobs:
timeout_minutes: 45 timeout_minutes: 45
max_attempts: 15 max_attempts: 15
env: env:
CI: true
FAIL_ON_CONSOLE_ERRORS: true FAIL_ON_CONSOLE_ERRORS: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4

View File

@ -28,43 +28,57 @@ jobs:
- run: yarn fmt-check - run: yarn fmt-check
yarn-build-wasm: yarn-build-wasm:
runs-on: ubuntu-22.04 # Build the wasm blob once on the fastest runner.
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'
- run: yarn install
- name: Install dependencies
run: yarn install
- name: Use correct Rust toolchain
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b - uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with: with:
tool: wasm-pack tool: wasm-pack
- run: yarn build:wasm
yarn-tsc: - name: Rust Cache
runs-on: ubuntu-22.04 uses: Swatinem/rust-cache@v2
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- run: yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install
- uses: Swatinem/rust-cache@v2
with: with:
workspaces: './rust' workspaces: './rust'
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b - name: Build Wasm
with: shell: bash
tool: wasm-pack run: yarn build:wasm
- run: yarn build:wasm
- run: yarn tsc
yarn-lint: - uses: actions/upload-artifact@v4
runs-on: ubuntu-22.04 with:
name: prepared-wasm
path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
- uses: actions/upload-artifact@v4
with:
name: prepared-ts-rs-bindings
path: |
rust/kcl-lib/bindings/*
yarn-tsc:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -73,7 +87,53 @@ jobs:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'
- run: yarn install - run: yarn install
- run: yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn tsc
yarn-lint:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn lint - run: yarn lint
python-codespell: python-codespell:
@ -91,6 +151,7 @@ jobs:
yarn-unit-test-kcl-samples: yarn-unit-test-kcl-samples:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: yarn-build-wasm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -103,7 +164,22 @@ jobs:
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b - uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with: with:
tool: wasm-pack tool: wasm-pack
- run: yarn build:wasm
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn simpleserver:bg - run: yarn simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
@ -120,6 +196,7 @@ jobs:
yarn-unit-test: yarn-unit-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: yarn-build-wasm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -132,7 +209,22 @@ jobs:
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b - uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with: with:
tool: wasm-pack tool: wasm-pack
- run: yarn build:wasm
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn simpleserver:bg - run: yarn simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
@ -141,13 +233,13 @@ jobs:
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: yarn playwright install chromium --with-deps run: yarn playwright install chromium --with-deps
- name: run unit tests - name: Run unit tests
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: xvfb-run -a yarn test:unit run: xvfb-run -a yarn test:unit
env: env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: check for changes - name: Check for changes
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
id: git-check id: git-check
run: | run: |

View File

@ -14,16 +14,32 @@ permissions:
jobs: jobs:
update-branch: update-branch:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- shell: bash with:
token: ${{ steps.app-token.outputs.token }}
- name: Sync with main
run: | run: |
# checkout our branch # Create the branch
git checkout all-e2e || git checkout -b all-e2e git checkout all-e2e || git checkout -b all-e2e
# fetch origin
# Reset to main
git fetch origin git fetch origin
# reset to main
git reset --hard origin/main git reset --hard origin/main
# force push it
# Get a new SHA to prevent overwriting the commit status on main
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit --allow-empty --message="[all-e2e] $(git log --max-count=1 --pretty=%B)"
# Overwrite the branch
git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}.git
git push --force origin all-e2e git push --force origin all-e2e

View File

@ -20,8 +20,12 @@ $(WASM_PACK):
############################################################################### ###############################################################################
# BUILD # BUILD
RUST_SOURCES := $(wildcard rust/*) $(wildcard rust/**/*) CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*)
TYPESCRIPT_SOURCES := $(wildcard src/**/*.tsx) $(wildcard src/**/*.ts) RUST_SOURCES := $(wildcard rust/**/*.rs)
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
.PHONY: build .PHONY: build
build: build-web build-desktop build: build-web build-desktop
@ -32,13 +36,13 @@ build-web: public/kcl_wasm_lib_bg.wasm build/index.html
.PHONY: build-desktop .PHONY: build-desktop
build-desktop: public/kcl_wasm_lib_bg.wasm .vite/build/main.js build-desktop: public/kcl_wasm_lib_bg.wasm .vite/build/main.js
public/kcl_wasm_lib_bg.wasm: $(RUST_SOURCES) public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES)$(RUST_SOURCES)
yarn build:wasm yarn build:wasm:dev
build/index.html: $(TYPESCRIPT_SOURCES) build/index.html: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
yarn build:local yarn build:local
.vite/build/main.js: $(TYPESCRIPT_SOURCES) .vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
yarn tronb:vite:dev yarn tronb:vite:dev
############################################################################### ###############################################################################
@ -73,19 +77,29 @@ run-desktop: install build-desktop ## Start the desktop app
############################################################################### ###############################################################################
# TEST # TEST
GREP ?= "" E2E_WORKERS ?= 1
E2E_FAILURES ?= 1
E2E_GREP ?= ""
.PHONY: test .PHONY: test
test: test-unit test-e2e test: test-unit test-e2e
.PHONY: test-unit .PHONY: test-unit
test-unit: install ## Run the unit tests test-unit: install ## Run the unit tests
@ nc -z localhost 3000 || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 ) @ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
yarn test:unit yarn test:unit
.PHONY: test-e2e .PHONY: test-e2e
test-e2e: install build-desktop ## Run the e2e tests test-e2e: test-e2e-desktop
yarn test:playwright:electron --workers=1 --grep=$(GREP)
.PHONY: test-e2e-web
test-e2e-web: install build-web ## Run the web e2e tests
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
yarn chrome:test --headed --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep=$(E2E_GREP)
.PHONY: test-e2e-desktop
test-e2e-desktop: install build-desktop ## Run the desktop e2e tests
yarn test:playwright:electron --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep="$(E2E_GREP)"
############################################################################### ###############################################################################
# CLEAN # CLEAN

File diff suppressed because one or more lines are too long

View File

@ -10,10 +10,10 @@ Construct a circle derived from 3 points.
```js ```js
circleThreePoint( circleThreePoint(
sketchSurfaceOrGroup: SketchOrSurface,
p1: [number], p1: [number],
p2: [number], p2: [number],
p3: [number], p3: [number],
sketchSurfaceOrGroup: SketchOrSurface,
tag?: TagDeclarator, tag?: TagDeclarator,
): Sketch ): Sketch
``` ```
@ -23,10 +23,10 @@ circleThreePoint(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
| `p1` | [`[number]`](/docs/kcl/types/number) | 1st point to derive the circle. | Yes | | `p1` | [`[number]`](/docs/kcl/types/number) | 1st point to derive the circle. | Yes |
| `p2` | [`[number]`](/docs/kcl/types/number) | 2nd point to derive the circle. | Yes | | `p2` | [`[number]`](/docs/kcl/types/number) | 2nd point to derive the circle. | Yes |
| `p3` | [`[number]`](/docs/kcl/types/number) | 3rd point to derive the circle. | Yes | | `p3` | [`[number]`](/docs/kcl/types/number) | 3rd point to derive the circle. | Yes |
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Identifier for the circle to reference elsewhere. | No | | [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Identifier for the circle to reference elsewhere. | No |
### Returns ### Returns

View File

@ -9,13 +9,9 @@ layout: manual
### `std` ### `std`
- [`HALF_TURN`](/docs/kcl/consts/std-HALF_TURN)
- [`QUARTER_TURN`](/docs/kcl/consts/std-QUARTER_TURN)
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-THREE_QUARTER_TURN)
- [`XY`](/docs/kcl/consts/std-XY) - [`XY`](/docs/kcl/consts/std-XY)
- [`XZ`](/docs/kcl/consts/std-XZ) - [`XZ`](/docs/kcl/consts/std-XZ)
- [`YZ`](/docs/kcl/consts/std-YZ) - [`YZ`](/docs/kcl/consts/std-YZ)
- [`ZERO`](/docs/kcl/consts/std-ZERO)
### `std::math` ### `std::math`
@ -23,3 +19,10 @@ layout: manual
- [`PI`](/docs/kcl/consts/std-math-PI) - [`PI`](/docs/kcl/consts/std-math-PI)
- [`TAU`](/docs/kcl/consts/std-math-TAU) - [`TAU`](/docs/kcl/consts/std-math-TAU)
### `std::turns`
- [`HALF_TURN`](/docs/kcl/consts/std-turns-HALF_TURN)
- [`QUARTER_TURN`](/docs/kcl/consts/std-turns-QUARTER_TURN)
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-turns-THREE_QUARTER_TURN)
- [`ZERO`](/docs/kcl/consts/std-turns-ZERO)

View File

@ -1,15 +0,0 @@
---
title: "std::HALF_TURN"
excerpt: ""
layout: manual
---
```js
std::HALF_TURN: number(deg) = 180deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::QUARTER_TURN: number(deg) = 90deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::THREE_QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::THREE_QUARTER_TURN: number(deg) = 270deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::ZERO"
excerpt: ""
layout: manual
---
```js
std::ZERO: number = 0
```

View File

@ -15,7 +15,7 @@ std::math::E: number = 2.71828182845904523536028747135266250_
### Examples ### Examples
```js ```js
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ |> angledLine({
angle = 30, angle = 30,

View File

@ -17,7 +17,7 @@ std::math::PI: number = 3.14159265358979323846264338327950288_
```js ```js
circumference = 70 circumference = 70
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn(XZ)
|> circle(center = [0, 0], radius = circumference/ (2 * PI)) |> circle(center = [0, 0], radius = circumference/ (2 * PI))
example = extrude(exampleSketch, length = 5) example = extrude(exampleSketch, length = 5)

View File

@ -15,7 +15,7 @@ std::math::TAU: number = 6.28318530717958647692528676655900577_
### Examples ### Examples
```js ```js
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ |> angledLine({
angle = 50, angle = 50,

View File

@ -0,0 +1,15 @@
---
title: "std::turns::HALF_TURN"
excerpt: ""
layout: manual
---
```js
std::turns::HALF_TURN: number(deg) = 180deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::turns::QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::turns::QUARTER_TURN: number(deg) = 90deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::turns::THREE_QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::turns::THREE_QUARTER_TURN: number(deg) = 270deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::turns::ZERO"
excerpt: ""
layout: manual
---
```js
std::turns::ZERO: number = 0
```

File diff suppressed because one or more lines are too long

View File

@ -23,19 +23,15 @@ layout: manual
* [`tag`](kcl/types/tag) * [`tag`](kcl/types/tag)
* **std** * **std**
* [`Face`](kcl/types/Face) * [`Face`](kcl/types/Face)
* [`HALF_TURN`](kcl/consts/std-HALF_TURN)
* [`Helix`](kcl/types/Helix) * [`Helix`](kcl/types/Helix)
* [`Plane`](kcl/types/Plane) * [`Plane`](kcl/types/Plane)
* [`Point2d`](kcl/types/Point2d) * [`Point2d`](kcl/types/Point2d)
* [`Point3d`](kcl/types/Point3d) * [`Point3d`](kcl/types/Point3d)
* [`QUARTER_TURN`](kcl/consts/std-QUARTER_TURN)
* [`Sketch`](kcl/types/Sketch) * [`Sketch`](kcl/types/Sketch)
* [`Solid`](kcl/types/Solid) * [`Solid`](kcl/types/Solid)
* [`THREE_QUARTER_TURN`](kcl/consts/std-THREE_QUARTER_TURN)
* [`XY`](kcl/consts/std-XY) * [`XY`](kcl/consts/std-XY)
* [`XZ`](kcl/consts/std-XZ) * [`XZ`](kcl/consts/std-XZ)
* [`YZ`](kcl/consts/std-YZ) * [`YZ`](kcl/consts/std-YZ)
* [`ZERO`](kcl/consts/std-ZERO)
* [`abs`](kcl/abs) * [`abs`](kcl/abs)
* [`acos`](kcl/acos) * [`acos`](kcl/acos)
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX) * [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
@ -61,7 +57,6 @@ layout: manual
* [`bezierCurve`](kcl/bezierCurve) * [`bezierCurve`](kcl/bezierCurve)
* [`ceil`](kcl/ceil) * [`ceil`](kcl/ceil)
* [`chamfer`](kcl/chamfer) * [`chamfer`](kcl/chamfer)
* [`circle`](kcl/circle)
* [`circleThreePoint`](kcl/circleThreePoint) * [`circleThreePoint`](kcl/circleThreePoint)
* [`close`](kcl/close) * [`close`](kcl/close)
* [`cm`](kcl/cm) * [`cm`](kcl/cm)
@ -145,3 +140,10 @@ layout: manual
* [`cos`](kcl/std-math-cos) * [`cos`](kcl/std-math-cos)
* [`sin`](kcl/std-math-sin) * [`sin`](kcl/std-math-sin)
* [`tan`](kcl/std-math-tan) * [`tan`](kcl/std-math-tan)
* **std::sketch**
* [`circle`](kcl/std-sketch-circle)
* **std::turns**
* [`turns::HALF_TURN`](kcl/consts/std-turns-HALF_TURN)
* [`turns::QUARTER_TURN`](kcl/consts/std-turns-QUARTER_TURN)
* [`turns::THREE_QUARTER_TURN`](kcl/consts/std-turns-THREE_QUARTER_TURN)
* [`turns::ZERO`](kcl/consts/std-turns-ZERO)

View File

@ -6,6 +6,10 @@ layout: manual
Rotate a solid or a sketch. Rotate a solid or a sketch.
This is really useful for assembling parts together. You can create a part and then rotate it to the correct orientation.
For sketches, you can use this to rotate a sketch and then loft it with another sketch.
### Using Roll, Pitch, and Yaw ### Using Roll, Pitch, and Yaw
When rotating a part in 3D space, "roll," "pitch," and "yaw" refer to the three rotational axes used to describe its orientation: roll is rotation around the longitudinal axis (front-to-back), pitch is rotation around the lateral axis (wing-to-wing), and yaw is rotation around the vertical axis (up-down); essentially, it's like tilting the part on its side (roll), tipping the nose up or down (pitch), and turning it left or right (yaw). When rotating a part in 3D space, "roll," "pitch," and "yaw" refer to the three rotational axes used to describe its orientation: roll is rotation around the longitudinal axis (front-to-back), pitch is rotation around the lateral axis (wing-to-wing), and yaw is rotation around the vertical axis (up-down); essentially, it's like tilting the part on its side (roll), tipping the nose up or down (pitch), and turning it left or right (yaw).
@ -166,7 +170,7 @@ fn square() {
profile001 = square() profile001 = square()
profile002 = square() profile002 = square()
|> translate(translate = [0, 0, 20]) |> translate(x = 0, y = 0, z = 20)
|> rotate(axis = [0, 0, 1.0], angle = 45) |> rotate(axis = [0, 0, 1.0], angle = 45)
loft([profile001, profile002]) loft([profile001, profile002])

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ Compute the cosine of a number (in radians).
```js ```js
cos(num: number(rad)): number(_) cos(@num: number(rad)): number(_)
``` ```
@ -27,7 +27,7 @@ cos(num: number(rad)): number(_)
### Examples ### Examples
```js ```js
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ |> angledLine({
angle = 30, angle = 30,

View File

@ -9,7 +9,7 @@ Compute the sine of a number (in radians).
```js ```js
sin(num: number(rad)): number(_) sin(@num: number(rad)): number(_)
``` ```
@ -27,7 +27,7 @@ sin(num: number(rad)): number(_)
### Examples ### Examples
```js ```js
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ |> angledLine({
angle = 50, angle = 50,

View File

@ -9,7 +9,7 @@ Compute the tangent of a number (in radians).
```js ```js
tan(num: number(rad)): number(_) tan(@num: number(rad)): number(_)
``` ```
@ -27,7 +27,7 @@ tan(num: number(rad)): number(_)
### Examples ### Examples
```js ```js
exampleSketch = startSketchOn("XZ") exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ |> angledLine({
angle = 50, angle = 50,

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

View File

@ -28,7 +28,7 @@ An extrude plane.
| `faceId` |[`string`](/docs/kcl/types/string)| The face id for the extrude plane. | No | | `faceId` |[`string`](/docs/kcl/types/string)| The face id for the extrude plane. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No | | [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No | | `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No | | `sourceRange` |`[integer, integer, integer]`| The source range. | No |
---- ----
@ -48,7 +48,7 @@ An extruded arc.
| `faceId` |[`string`](/docs/kcl/types/string)| The face id for the extrude plane. | No | | `faceId` |[`string`](/docs/kcl/types/string)| The face id for the extrude plane. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No | | [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No | | `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No | | `sourceRange` |`[integer, integer, integer]`| The source range. | No |
---- ----
@ -68,7 +68,7 @@ Geometry metadata.
| `faceId` |[`string`](/docs/kcl/types/string)| The id for the chamfer surface. | No | | `faceId` |[`string`](/docs/kcl/types/string)| The id for the chamfer surface. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No | | [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No | | `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No | | `sourceRange` |`[integer, integer, integer]`| The source range. | No |
---- ----
@ -88,7 +88,7 @@ Geometry metadata.
| `faceId` |[`string`](/docs/kcl/types/string)| The id for the fillet surface. | No | | `faceId` |[`string`](/docs/kcl/types/string)| The id for the fillet surface. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No | | [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No | | `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No | | `sourceRange` |`[integer, integer, integer]`| The source range. | No |
---- ----

View File

@ -17,6 +17,6 @@ Geometry metadata.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No | | `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No | | `sourceRange` |`[integer, integer, integer]`| The source range. | No |

View File

@ -17,7 +17,7 @@ A helix.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `value` |[`string`](/docs/kcl/types/string)| The id of the helix. | No | | `value` |[`string`](/docs/kcl/types/string)| The id of the helix. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No | | `artifactId` |[`string`](/docs/kcl/types/string)| The artifact ID. | No |
| `revolutions` |[`number`](/docs/kcl/types/number)| Number of revolutions. | No | | `revolutions` |[`number`](/docs/kcl/types/number)| Number of revolutions. | No |
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No | | `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |

View File

@ -285,7 +285,7 @@ Data for an imported geometry.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Module`| | No | | `type` |enum: `Module`| | No |
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No | | `value` |`integer`| Identifier of a source file. Uses a u32 to keep the size small. | No |
---- ----

View File

@ -10,8 +10,8 @@ A point in two dimensional space.
type Point2d = [number; 2] type Point2d = [number; 2]
``` ```
`Point2d` is an alias for a two-element array of [number](/docs/kcl/types/number)s. To write a value [`Point2d`](/docs/kcl/types/Point2d) is an alias for a two-element array of [number](/docs/kcl/types/number)s. To write a value
with type `Point2d`, use an array, e.g., `[0, 0]` or `[5.0, 3.14]`. with type [`Point2d`](/docs/kcl/types/Point2d), use an array, e.g., `[0, 0]` or `[5.0, 3.14]`.

View File

@ -10,8 +10,8 @@ A point in three dimensional space.
type Point3d = [number; 3] type Point3d = [number; 3]
``` ```
`Point3d` is an alias for a three-element array of [number](/docs/kcl/types/number)s. To write a value [`Point3d`](/docs/kcl/types/Point3d) is an alias for a three-element array of [number](/docs/kcl/types/number)s. To write a value
with type `Point3d`, use an array, e.g., `[0, 0, 0]` or `[5.0, 3.14, 6.8]`. with type [`Point3d`](/docs/kcl/types/Point3d), use an array, e.g., `[0, 0, 0]` or `[5.0, 3.14, 6.8]`.

View File

@ -17,6 +17,6 @@ Data for polar coordinates.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `angle` |[`number`](/docs/kcl/types/number)| The angle of the line (in degrees). | No | | `angle` |[`number`](/docs/kcl/types/number)| The angle of the line (in degrees). | No |
| `length` |[`TyF64`](/docs/kcl/types/TyF64)| The length of the line. | No | | `length` |[`number`](/docs/kcl/types/number)| The length of the line. | No |

View File

@ -17,7 +17,7 @@ mySketch = startSketchOn('XY')
|> close() |> close()
``` ```
The `mySketch` variable will be an executed `Sketch` object. Executed being past The `mySketch` variable will be an executed [`Sketch`](/docs/kcl/types/Sketch) object. Executed being past
tense, because the engine has already executed the commands to create the sketch. tense, because the engine has already executed the commands to create the sketch.
The previous sketch commands will never be executed again, in this case. The previous sketch commands will never be executed again, in this case.

View File

@ -25,7 +25,7 @@ A sketch type.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `plane`| | No | | `type` |enum: `plane`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the plane. | No | | `id` |[`string`](/docs/kcl/types/string)| The id of the plane. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No | | `artifactId` |[`string`](/docs/kcl/types/string)| The artifact ID. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Type for a plane. | No | | `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Type for a plane. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No | | `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No | | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
@ -49,7 +49,7 @@ A face.
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `face`| | No | | `type` |enum: `face`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the face. | No | | `id` |[`string`](/docs/kcl/types/string)| The id of the face. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No | | `artifactId` |[`string`](/docs/kcl/types/string)| The artifact ID. | No |
| `value` |[`string`](/docs/kcl/types/string)| The tag of the face. | No | | `value` |[`string`](/docs/kcl/types/string)| The tag of the face. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No | | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |

View File

@ -18,7 +18,7 @@ myPart = startSketchOn('XY')
|> extrude(length = 6) |> extrude(length = 6)
``` ```
The `myPart` variable will be an executed `Solid` object. Executed being past The `myPart` variable will be an executed [`Solid`](/docs/kcl/types/Solid) object. Executed being past
tense, because the engine has already executed the commands to create the solid. tense, because the engine has already executed the commands to create the solid.
The previous solid commands will never be executed again, in this case. The previous solid commands will never be executed again, in this case.

View File

@ -1,15 +0,0 @@
---
title: "SourceRange"
excerpt: ""
layout: manual
---
**Type:** `integer` (`uint`)

View File

@ -5,17 +5,11 @@ layout: manual
--- ---
**Type:** `object` **Type:** [`number`](/docs/kcl/types/number) (`double`)
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `n` |[`number`](/docs/kcl/types/number)| | No |
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No |

View File

@ -85,7 +85,7 @@ async function doBasicSketch(
await page.mouse.click(startXPx, 500 - PUR * 20) await page.mouse.click(startXPx, 500 - PUR * 20)
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect(u.codeLocator) await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${ .toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt commonPoints.startAt
}, sketch001) }, sketch001)
|> xLine(length = ${commonPoints.num1}) |> xLine(length = ${commonPoints.num1})
@ -119,10 +119,7 @@ async function doBasicSketch(
await page.waitForTimeout(100) await page.waitForTimeout(100)
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect( expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)
).toBeLessThan(3)
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
} }
// hold down shift // hold down shift
@ -145,7 +142,7 @@ async function doBasicSketch(
// Open the code pane. // Open the code pane.
await u.openKclCodePanel() await u.openKclCodePanel()
await expect(u.codeLocator) await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${ .toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt commonPoints.startAt
}, sketch001) }, sketch001)
|> xLine(length = ${commonPoints.num1}, tag = $seg01) |> xLine(length = ${commonPoints.num1}, tag = $seg01)

View File

@ -0,0 +1,117 @@
import { test, expect } from './zoo-test'
import fs from 'node:fs/promises'
import path from 'node:path'
test.describe('Point and click for boolean workflows', () => {
// Boolean operations to test
const booleanOperations = [
{
name: 'union',
code: 'union([extrude001, extrude006])',
},
{
name: 'subtract',
code: 'subtract([extrude001], tools = [extrude006])',
},
{
name: 'intersect',
code: 'intersect([extrude001, extrude006])',
},
] as const
for (let i = 0; i < booleanOperations.length; i++) {
const operation = booleanOperations[i]
const operationName = operation.name
const commandName = `Boolean ${
operationName.charAt(0).toUpperCase() + operationName.slice(1)
}`
test(`Create boolean operation -- ${operationName}`, async ({
context,
homePage,
cmdBar,
editor,
toolbar,
scene,
page,
}) => {
const file = await fs.readFile(
path.resolve(
__dirname,
'../../',
'./rust/kcl-lib/e2e/executor/inputs/boolean-setup-with'
),
'utf-8'
)
await context.addInitScript((file) => {
localStorage.setItem('persistCode', file)
}, file)
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
await scene.settled(cmdBar)
// Test coordinates for selection - these might need adjustment based on actual scene layout
const cylinderPoint = { x: 592, y: 174 }
const secondObjectPoint = { x: 683, y: 273 }
// Create mouse helpers for selecting objects
const [clickFirstObject] = scene.makeMouseHelpers(
cylinderPoint.x,
cylinderPoint.y,
{ steps: 10 }
)
const [clickSecondObject] = scene.makeMouseHelpers(
secondObjectPoint.x,
secondObjectPoint.y,
{ steps: 10 }
)
await test.step(`Test ${operationName} operation`, async () => {
// Click the boolean operation button in the toolbar
await toolbar.selectBoolean(operationName)
// Verify command bar is showing the right command
await expect(cmdBar.page.getByTestId('command-name')).toContainText(
commandName
)
// Select first object in the scene, expect there to be a pixel diff from the selection color change
await clickFirstObject({ pixelDiff: 50 })
// For subtract, we need to proceed to the next step before selecting the second object
if (operationName !== 'subtract') {
// should down shift key to select multiple objects
await page.keyboard.down('Shift')
}
// Select second object
await clickSecondObject({ pixelDiff: 50 })
// Confirm the operation in the command bar
await cmdBar.progressCmdBar()
if (operationName === 'union' || operationName === 'intersect') {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Solids: '2 paths',
},
commandName,
})
} else if (operationName === 'subtract') {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Tool: '1 path',
Target: '1 path',
},
commandName,
})
}
await cmdBar.submit()
await editor.expectEditor.toContain(operation.code)
})
})
}
})

View File

@ -46,7 +46,7 @@ test.describe(
}, },
} }
const code = `sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)` const code = `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)`
await u.openDebugPanel() await u.openDebugPanel()

View File

@ -21,14 +21,15 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
await page.addInitScript(() => { await page.addInitScript(() => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`// Extruded Triangle `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ) // Extruded Triangle
|> startProfileAt([0, 0], %) sketch001 = startSketchOn(XZ)
|> line(end = [10, 0]) |> startProfileAt([0, 0], %)
|> line(end = [-5, 10]) |> line(end = [10, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(end = [-5, 10])
|> close() |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
extrude001 = extrude(sketch001, length = 5)` |> close()
extrude001 = extrude(sketch001, length = 5)`
) )
}) })
@ -250,11 +251,11 @@ test(
]) ])
await Promise.all([ await Promise.all([
fsp.copyFile( fsp.copyFile(
executorInputPath('router-template-slate.kcl'), executorInputPath('cylinder-inches.kcl'),
join(routerTemplateDir, 'main.kcl') join(routerTemplateDir, 'main.kcl')
), ),
fsp.copyFile( fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
join(bracketDir, 'main.kcl') join(bracketDir, 'main.kcl')
), ),
]) ])

View File

@ -513,7 +513,8 @@ c = 3 + a`
await homePage.openProject(projectName) await homePage.openProject(projectName)
// TODO: you probably shouldn't need an engine connection to add a parameter, // TODO: you probably shouldn't need an engine connection to add a parameter,
// but you do because all modeling commands have that requirement // but you do because all modeling commands have that requirement
await scene.settled(cmdBar) // Don't use scene.settled here
await expect(scene.startEditSketchBtn).toBeEnabled({ timeout: 15_000 })
await test.step(`Create a parameter via command bar`, async () => { await test.step(`Create a parameter via command bar`, async () => {
await cmdBar.cmdBarOpenBtn.click() await cmdBar.cmdBarOpenBtn.click()
@ -542,7 +543,12 @@ c = 3 + a`
) )
const newValue = `2 * b + a` const newValue = `2 * b + a`
await test.step(`Edit the parameter via command bar`, async () => { await test.step(`Edit the parameter via command bar`, async () => {
// TODO: make the command palette command registration more static, and the enabled state more dynamic
// so that we can just open the command palette and know all commands will be there.
await expect(scene.startEditSketchBtn).toBeEnabled()
await cmdBar.cmdBarOpenBtn.click() await cmdBar.cmdBarOpenBtn.click()
await cmdBar.chooseCommand('edit parameter') await cmdBar.chooseCommand('edit parameter')
await cmdBar.expectState({ await cmdBar.expectState({

View File

@ -20,11 +20,11 @@ test(
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })]) await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
await Promise.all([ await Promise.all([
fsp.copyFile( fsp.copyFile(
executorInputPath('router-template-slate.kcl'), executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'other.kcl') path.join(bracketDir, 'other.kcl')
), ),
fsp.copyFile( fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
path.join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
), ),
]) ])
@ -107,7 +107,7 @@ test(
}, },
{ timeout: 15_000 } { timeout: 15_000 }
) )
.toBeGreaterThan(300_000) .toBeGreaterThan(30_000)
}) })
}) })
@ -187,7 +187,7 @@ test(
}, },
{ timeout: 15_000 } { timeout: 15_000 }
) )
.toBeGreaterThan(70_000) .toBeGreaterThan(50_000)
}) })
}) })
} }

View File

@ -32,26 +32,30 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('/') await page.keyboard.press('/')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content')).toHaveText(
.toHaveText(`sketch001 = startSketchOn(XY) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line(end = [20, 0]) |> line(end = [20, 0])
|> line(end = [0, 20]) |> line(end = [0, 20])
|> line(end = [-20, 0]) |> line(end = [-20, 0])
// |> close()`) // |> close()`.replaceAll('\n', '')
)
// uncomment the code // uncomment the code
await page.keyboard.down('ControlOrMeta') await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('/') await page.keyboard.press('/')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content')).toHaveText(
.toHaveText(`sketch001 = startSketchOn(XY) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line(end = [20, 0]) |> line(end = [20, 0])
|> line(end = [0, 20]) |> line(end = [0, 20])
|> line(end = [-20, 0]) |> line(end = [-20, 0])
|> close()`) |> close()`.replaceAll('\n', '')
)
}) })
test('ensure we use the cache, and do not re-execute', async ({ test('ensure we use the cache, and do not re-execute', async ({
@ -178,13 +182,15 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await page.locator('#code-pane button:first-child').click() await page.locator('#code-pane button:first-child').click()
await page.locator('button:has-text("Format code")').click() await page.locator('button:has-text("Format code")').click()
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content')).toHaveText(
.toHaveText(`sketch001 = startSketchOn(XY) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line(end = [20, 0]) |> line(end = [20, 0])
|> line(end = [0, 20]) |> line(end = [0, 20])
|> line(end = [-20, 0]) |> line(end = [-20, 0])
|> close()`) |> close()`.replaceAll('\n', '')
)
}) })
test('if you click the format button it formats your code and executes so lints are still there', async ({ test('if you click the format button it formats your code and executes so lints are still there', async ({
@ -227,13 +233,15 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel() await u.closeDebugPanel()
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content')).toHaveText(
.toHaveText(`sketch_001 = startSketchOn(XY) `@settings(defaultLengthUnit = in)
sketch_001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line(end = [20, 0]) |> line(end = [20, 0])
|> line(end = [0, 20]) |> line(end = [0, 20])
|> line(end = [-20, 0]) |> line(end = [-20, 0])
|> close()`) |> close()`.replaceAll('\n', '')
)
// error in guter // error in guter
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible() await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
@ -471,6 +479,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
test('if you write kcl with lint errors you get lints', async ({ test('if you write kcl with lint errors you get lints', async ({
page, page,
homePage, homePage,
scene,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
@ -490,10 +499,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('ArrowLeft') await page.keyboard.press('ArrowLeft')
await page.keyboard.press('ArrowRight') await page.keyboard.press('ArrowRight')
// FIXME: lsp errors do not propagate to the frontend until engine is connected and code is executed await scene.waitForExecutionDone()
// This timeout is to wait for engine connection. LSP and code execution errors should be handled differently
// LSP can emit errors as fast as it waits and show them in the editor
await page.waitForTimeout(10000)
// error in guter // error in guter
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible() await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
@ -815,10 +821,12 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
// there shouldn't be any auto complete options for 'lin' in the comment // there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible() await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content')).toHaveText(
.toHaveText(`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([3.14, 12], %) |> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`) |> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
// expect there to be no KCL errors // expect there to be no KCL errors
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0) await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
@ -888,10 +896,12 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
// there shouldn't be any auto complete options for 'lin' in the comment // there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible() await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content')).toHaveText(
.toHaveText(`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([3.14, 12], %) |> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`) |> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
}) })
}) })
test('Can undo a click and point extrude with ctrl+z', async ({ test('Can undo a click and point extrude with ctrl+z', async ({
@ -1206,4 +1216,55 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
}) })
} }
) )
test('Rectangle tool panning with middle click', async ({
page,
homePage,
toolbar,
scene,
cmdBar,
editor,
}) => {
await page.setBodyDimensions({ width: 1200, height: 900 })
await homePage.goToModelingScene()
// wait until scene is ready to be interacted with
await scene.connectionEstablished()
await scene.settled(cmdBar)
await page.getByRole('button', { name: 'Start Sketch' }).click()
// select an axis plane
await page.mouse.click(700, 200)
// Needed as we don't yet have a way to get a signal from the engine that the camera has animated to the sketch plane
await page.waitForTimeout(1000)
const middleMousePan = async (
startX: number,
startY: number,
endX: number,
endY: number
) => {
const initialCode = await editor.getCurrentCode()
await page.mouse.click(startX, startY, { button: 'middle' })
await page.mouse.move(endX, endY, {
steps: 10,
})
// We expect the code to be the same, middle mouse click should not modify the code, only do panning
await editor.expectEditor.toBe(initialCode)
}
await test.step(`Verify corner rectangle panning`, async () => {
await page.getByTestId('corner-rectangle').click()
await middleMousePan(800, 500, 900, 600)
})
await test.step(`Verify center rectangle panning`, async () => {
await toolbar.selectCenterRectangle()
await middleMousePan(800, 200, 900, 300)
})
})
}) })

View File

@ -6,6 +6,7 @@ import {
executorInputPath, executorInputPath,
getUtils, getUtils,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
runningOnWindows,
} from './test-utils' } from './test-utils'
import { join } from 'path' import { join } from 'path'
import { FILE_EXT } from 'lib/constants' import { FILE_EXT } from 'lib/constants'
@ -15,6 +16,9 @@ test.describe('integrations tests', () => {
'Creating a new file or switching file while in sketchMode should exit sketchMode', 'Creating a new file or switching file while in sketchMode should exit sketchMode',
{ tag: '@electron' }, { tag: '@electron' },
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => { async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
if (runningOnWindows()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'test-sample') const bracketDir = join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })

View File

@ -81,6 +81,13 @@ export class EditorFixture {
expectEditor = { expectEditor = {
toContain: this._expectEditorToContain(), toContain: this._expectEditorToContain(),
not: { toContain: this._expectEditorToContain(true) }, not: { toContain: this._expectEditorToContain(true) },
toBe: async (code: string) => {
const currentCode = await this.getCurrentCode()
return expect(currentCode).toBe(code)
},
}
getCurrentCode = async () => {
return await this.codeContent.innerText()
} }
snapshot = async (options?: { timeout?: number; name?: string }) => { snapshot = async (options?: { timeout?: number; name?: string }) => {
const wasPaneOpen = await this.checkIfPaneIsOpen() const wasPaneOpen = await this.checkIfPaneIsOpen()

View File

@ -310,7 +310,9 @@ export async function expectPixelColor(
.toBeTruthy() .toBeTruthy()
.catch((cause) => { .catch((cause) => {
throw new Error( throw new Error(
`ExpectPixelColor: expecting ${colour} got ${finalValue}`, `ExpectPixelColor: point ${JSON.stringify(
coords
)} was expecting ${colour} but got ${finalValue}`,
{ cause } { cause }
) )
}) })

View File

@ -181,6 +181,14 @@ export class ToolbarFixture {
).toBeVisible() ).toBeVisible()
await this.page.getByTestId('dropdown-center-rectangle').click() await this.page.getByTestId('dropdown-center-rectangle').click()
} }
selectBoolean = async (operation: 'union' | 'subtract' | 'intersect') => {
await this.page
.getByRole('button', { name: 'caret down Union: open menu' })
.click()
const operationTestId = `dropdown-boolean-${operation}`
await expect(this.page.getByTestId(operationTestId)).toBeVisible()
await this.page.getByTestId(operationTestId).click()
}
selectCircleThreePoint = async () => { selectCircleThreePoint = async () => {
await this.page await this.page

View File

@ -257,6 +257,29 @@ export const isErrorWhitelisted = (exception: Error) => {
project: 'Google Chrome', project: 'Google Chrome',
foundInSpec: 'e2e/playwright/testing-settings.spec.ts', foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
}, },
// TODO: fix this error in the code
{
name: 'ReferenceError',
message: '_testUtils is not defined',
stack: '',
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/snapshot-tests.spec.ts',
},
{
name: 'TypeError',
message: 'Failed to fetch',
stack: '',
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/snapshot-tests.spec.ts',
},
{
name: 'Error',
message: 'The "path" argument must be of type string. Received undefined',
stack:
'Error: The "path" argument must be of type string. Received undefined',
project: 'Google Chrome',
foundInSpec: '', // many tests are impacted by this error
},
] ]
const cleanString = (str: string) => str.replace(/[`"]/g, '') const cleanString = (str: string) => str.replace(/[`"]/g, '')

View File

@ -11,7 +11,7 @@ test(
const bracketDir = join(dir, 'bracket') const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
join(bracketDir, 'main.kcl') join(bracketDir, 'main.kcl')
) )
}) })
@ -51,7 +51,7 @@ test(
const bracketDir = join(dir, 'bracket') const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
join(bracketDir, 'main.kcl') join(bracketDir, 'main.kcl')
) )
}) })

View File

@ -0,0 +1,292 @@
import { test, expect } from './zoo-test'
import { PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
import * as fsp from 'fs/promises'
import { join } from 'path'
import {
createProject,
tomlToPerProjectSettings,
perProjectsettingsToToml,
} from './test-utils'
import { NamedView } from '@rust/kcl-lib/bindings/NamedView'
// Helper function to determine if the file path on disk exists
// Specifically this is used to check if project.toml exists on disk
const fileExists = async (path: string) => {
return !!(await fsp
.stat(path)
.then((_) => true)
.catch((_) => false))
}
// Here are a few uuids.
// When created named views rust will auto generate uuids and they will
// never match the snapshots. Overwrite them in memory to these
// values to have them match the snapshots.
const uuid1: string = '0656fb1a-9640-473e-b334-591dc70c0138'
const uuid2: string = 'c810cf04-c6cc-4a4a-8b11-17bf445dcab7'
const uuid3: string = 'cfecbfee-48a6-4561-b96d-ffbe5678bb7d'
// Look up the named view by name and then rewrite it with the same uuid each time
const nameToUuid: Map<string, string> = new Map()
nameToUuid.set('uuid1', uuid1)
nameToUuid.set('uuid2', uuid2)
nameToUuid.set('uuid3', uuid3)
/**
* Given the project.toml string, overwrite the named views to be the constant uuid
* values to match the snapshots. The uuids are randomly generated
*/
function tomlStringOverWriteNamedViewUuids(toml: string): string {
const settings = tomlToPerProjectSettings(toml)
const namedViews = settings.settings?.app?.named_views
if (namedViews) {
const entries = Object.entries(namedViews)
const remappedNamedViews: { [key: string]: NamedView } = {}
entries.forEach(([_, value]) => {
if (value) {
// {name:'uuid1'} -> uuid1 lookup
const staticUuid = nameToUuid.get(value.name)
if (staticUuid) {
remappedNamedViews[staticUuid] = value
}
}
})
if (settings && settings.settings && settings.settings.app) {
settings.settings.app.named_views = remappedNamedViews
}
}
return perProjectsettingsToToml(settings)
}
test.describe('Named view tests', () => {
test('Verify project.toml is not created', async ({ page }, testInfo) => {
// Create project and load it
const projectName = 'named-views'
await createProject({ name: projectName, page })
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// project.toml should not exist on initial project creation
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(false)
})
test('Verify named view gets created', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView = 'uuid1'
// Create and load project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create named view
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
// Generate paths for the project.toml
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Expect project.toml to be generated on disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
})
test('Verify named view gets deleted', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView1 = 'uuid1'
const myNamedView2 = 'uuid2'
// Create project and go into the project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create a new named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView1)
await cmdBar.progressCmdBar(false)
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Except the project.toml to be written to disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
// Delete a named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('delete named view')
cmdBar.selectOption({ name: myNamedView2 })
await cmdBar.progressCmdBar(false)
// Read project.toml into memory again since we deleted a named view
tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// // Write the entire tomlString to a snapshot.
// // There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-deleted')
})
test('Verify named view gets loaded', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView = 'uuid1'
// Create project and go into the project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create a new named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Except the project.toml to be written to disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
// Create a load a named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('load named view')
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
// Check the toast appeared
await expect(
page.getByText(`Named view ${myNamedView} loaded.`)
).toBeVisible()
})
test('Verify two named views get created', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView1 = 'uuid1'
const myNamedView2 = 'uuid2'
// Create and load project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create named view
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView1)
await cmdBar.progressCmdBar(false)
await page.waitForTimeout(1000)
const orbitMouseStart = { x: 800, y: 130 }
const orbitMouseEnd = { x: 0, y: 130 }
await page.mouse.move(orbitMouseStart.x, orbitMouseStart.y)
await page.mouse.down({ button: 'middle' })
await page.mouse.move(orbitMouseEnd.x, orbitMouseEnd.y, {
steps: 3,
})
await page.mouse.up({ button: 'middle' })
await page.waitForTimeout(1000)
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView2)
await cmdBar.progressCmdBar(false)
// Wait a moment for the project.toml to get written to disk with the new view point
await page.waitForTimeout(1000)
// Generate paths for the project.toml
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Expect project.toml to be generated on disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-two-named-view-gets-created')
})
})

View File

@ -0,0 +1,16 @@
[settings]
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -0,0 +1,16 @@
[settings]
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -0,0 +1,28 @@
[settings]
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1
[settings.app.named_views.c810cf04-c6cc-4a4a-8b11-17bf445dcab7]
name = "uuid2"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 1_826.5239, 0.0, 0.0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -0,0 +1,312 @@
import { test, expect } from './zoo-test'
/**
* Not all menu actions are tested. Some are default electron menu actions.
* Test file menu actions that trigger something in the frontend
*/
test.describe('Native file menu', { tag: ['@electron'] }, () => {
test.describe('Home page', () => {
test.describe('File role', () => {
test('File.Create project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const newProject =
app.applicationMenu.getMenuItemById('File.New project')
if (!newProject) fail()
newProject.click()
})
// Check that the command bar is opened
await expect(cmdBar.cmdBarElement).toBeVisible()
// Check the placeholder project name exists
const actualArgument = await cmdBar.cmdBarElement
.getByTestId('cmd-bar-arg-value')
.inputValue()
const expectedArgument = 'project-$nnn'
expect(actualArgument).toBe(expectedArgument)
})
test('File.Open project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const openProject =
app.applicationMenu.getMenuItemById('File.Open project')
if (!openProject) fail()
openProject.click()
})
// Check that the command bar is opened
await expect(cmdBar.cmdBarElement).toBeVisible()
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Open project'
expect(actual).toBe(expected)
})
test('File.Preferences.User settings', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const userSettings = app.applicationMenu.getMenuItemById(
'File.Preferences.User settings'
)
if (!userSettings) fail()
userSettings.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the user tab
const actualText = settings.getByText(
'The overall appearance of the app'
)
await expect(actualText).toBeVisible()
})
test('File.Preferences.Keybindings', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const keybindings = app.applicationMenu.getMenuItemById(
'File.Preferences.Keybindings'
)
if (!keybindings) fail()
keybindings.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the keybindings tab
const enterSketchMode = settings.locator('#enter-sketch-mode')
await expect(enterSketchMode).toBeVisible()
})
test('File.Preferences.User default units', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'File.Preferences.User default units'
)
if (!menu) fail()
menu.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
const defaultUnit = settings.locator('#defaultUnit')
await expect(defaultUnit).toBeVisible()
})
test('File.Preferences.Theme', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'File.Preferences.Theme'
)
if (!menu) fail()
menu.click()
})
// Check that the command bar is opened
await expect(cmdBar.cmdBarElement).toBeVisible()
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Settings · app · theme'
expect(actual).toBe(expected)
})
test('File.Preferences.Theme color', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'File.Preferences.Theme color'
)
if (!menu) fail()
menu.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
const defaultUnit = settings.locator('#themeColor')
await expect(defaultUnit).toBeVisible()
})
test('File.Preferences.Sign out', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById('File.Sign out')
if (!menu) fail()
// FIXME: Add back when you can actually sign out
// menu.click()
})
// FIXME: When signing out during E2E the page is not bound correctly.
// It cannot find the button
// const signIn = page.getByTestId('sign-in-button')
// await expect(signIn).toBeVisible()
})
})
test.describe('Edit role', () => {
test('Edit.Rename project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Edit.Rename project'
)
if (!menu) fail()
menu.click()
})
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Rename project'
expect(actual).toBe(expected)
})
test('Edit.Delete project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Edit.Delete project'
)
if (!menu) fail()
menu.click()
})
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Delete project'
expect(actual).toBe(expected)
})
test('Edit.Change project directory', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Edit.Change project directory'
)
if (!menu) fail()
menu.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
const projectDirectory = settings.locator('#projectDirectory')
await expect(projectDirectory).toBeVisible()
})
})
test.describe('View role', () => {
test('View.Command Palette...', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'View.Command Palette...'
)
if (!menu) fail()
menu.click()
})
// Check the placeholder project name exists
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
await expect(actual).toBeVisible()
})
})
test.describe('Help role', () => {
test('Help.Show all commands', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Help.Show all commands'
)
if (!menu) fail()
menu.click()
})
// Check the placeholder project name exists
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
await expect(actual).toBeVisible()
})
test('Help.KCL code samples', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Help.KCL code samples'
)
if (!menu) fail()
})
})
test('Help.Refresh and report a bug', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Help.Refresh and report a bug'
)
if (!menu) fail()
menu.click()
})
// Core dump and refresh magic number timeout
await page.waitForTimeout(7000)
const actual = page.getByText(
'No Projects found, ready to make your first one?'
)
await expect(actual).toBeVisible()
})
test('Help.Reset onboarding', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Help.Reset onboarding'
)
if (!menu) fail()
menu.click()
})
const actual = page.getByText(
`This is a hardware design tool that lets you edit visually, with code, or both. It's powered by the KittyCAD Design API, the first API created for anyone to build hardware design tools.`
)
await expect(actual).toBeVisible()
})
})
})
})

View File

@ -230,9 +230,9 @@ test.describe('Onboarding tests', () => {
// Override beforeEach test setup // Override beforeEach test setup
await context.addInitScript( await context.addInitScript(
async ({ settingsKey, settings }) => { async ({ settingsKey, settings, code }) => {
// Give some initial code, so we can test that it's cleared // Give some initial code, so we can test that it's cleared
localStorage.setItem('persistCode', originalCode) localStorage.setItem('persistCode', code)
localStorage.setItem(settingsKey, settings) localStorage.setItem(settingsKey, settings)
}, },
{ {
@ -240,6 +240,7 @@ test.describe('Onboarding tests', () => {
settings: settingsToToml({ settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_EXPORT, settings: TEST_SETTINGS_ONBOARDING_EXPORT,
}), }),
code: originalCode,
} }
) )

View File

@ -137,7 +137,7 @@ test.describe('Point-and-click tests', () => {
await scene.moveCameraTo(cameraPos, cameraTarget) await scene.moveCameraTo(cameraPos, cameraTarget)
await test.step('check chamfer selection changes cursor positon', async () => { await test.step('check chamfer selection changes cursor position', async () => {
await expect(async () => { await expect(async () => {
// sometimes initial click doesn't register // sometimes initial click doesn't register
await clickChamfer() await clickChamfer()
@ -173,7 +173,7 @@ test.describe('Point-and-click tests', () => {
}) })
await test.step('Check there is no errors after code created in previous steps executes', async () => { await test.step('Check there is no errors after code created in previous steps executes', async () => {
await editor.expectState({ await editor.expectState({
activeLines: ['sketch001 = startSketchOn(XZ)'], activeLines: ['@settings(defaultLengthUnit = in)'],
highlightedCode: '', highlightedCode: '',
diagnostics: [], diagnostics: [],
}) })
@ -299,7 +299,8 @@ test.describe('Point-and-click tests', () => {
await test.step('verify at the end of the test that final code is what is expected', async () => { await test.step('verify at the end of the test that final code is what is expected', async () => {
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag] |> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|> angledLine([0, 268.43], %, $rectangleSegmentA001) |> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([ |> angledLine([
@ -369,7 +370,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
}) })
}) })
test('Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array', async ({ test('Works on chamfers that are not in a pipeExpression can break up multi edges in a chamfer array', async ({
context, context,
page, page,
homePage, homePage,
@ -418,7 +419,8 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|>close()`, |>close()`,
}) })
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([75.8, 317.2], %) |> startProfileAt([75.8, 317.2], %)
|> angledLine([0, 268.43], %, $rectangleSegmentA001) |> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([ |> angledLine([
@ -1071,7 +1073,7 @@ openSketch = startSketchOn(XY)
}) })
}) })
test('Helix point-and-click', async ({ test('Helix point-and-click on default axis', async ({
context, context,
page, page,
homePage, homePage,
@ -1082,29 +1084,25 @@ openSketch = startSketchOn(XY)
}) => { }) => {
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 } const testPoint = { x: 620, y: 257 }
const expectedOutput = `helix001 = helix( revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5,)` const expectedOutput = `helix001 = helix( axis = 'X', radius = 5, length = 5, revolutions = 1, angleStart = 360, ccw = false,)`
const expectedLine = `revolutions=1,` const expectedLine = `axis='X',`
await homePage.goToModelingScene() await homePage.goToModelingScene()
// await test.step(`Look for the red of the default plane`, async () => {
// await scene.expectPixelColor([96, 52, 52], testPoint, 15)
// })
await test.step(`Go through the command bar flow`, async () => { await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click() await toolbar.helixButton.click()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'revolutions', currentArgKey: 'mode',
currentArgValue: '1', currentArgValue: '',
headerArguments: { headerArguments: {
Mode: '',
AngleStart: '', AngleStart: '',
Axis: '',
CounterClockWise: '',
Length: '',
Radius: '',
Revolutions: '', Revolutions: '',
Radius: '',
CounterClockWise: '',
}, },
highlightedHeaderArg: 'revolutions', highlightedHeaderArg: 'mode',
commandName: 'Helix', commandName: 'Helix',
}) })
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
@ -1113,6 +1111,19 @@ openSketch = startSketchOn(XY)
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Mode: 'Axis',
Axis: 'X',
AngleStart: '360',
Revolutions: '1',
Length: '5',
Radius: '5',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
}) })
@ -1136,30 +1147,31 @@ openSketch = startSketchOn(XY)
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Helix', commandName: 'Helix',
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'CounterClockWise',
currentArgValue: initialInput, currentArgValue: '',
headerArguments: { headerArguments: {
AngleStart: '360',
Axis: 'X', Axis: 'X',
CounterClockWise: '', AngleStart: '360',
Length: initialInput,
Radius: '5',
Revolutions: '1', Revolutions: '1',
Radius: '5',
Length: initialInput,
CounterClockWise: '',
}, },
highlightedHeaderArg: 'length', highlightedHeaderArg: 'CounterClockWise',
}) })
await page.keyboard.press('Shift+Backspace')
await expect(cmdBar.currentArgumentInput).toBeVisible() await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput) await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput)
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
AngleStart: '360',
Axis: 'X', Axis: 'X',
CounterClockWise: '', AngleStart: '360',
Length: newInput,
Radius: '5',
Revolutions: '1', Revolutions: '1',
Radius: '5',
Length: newInput,
CounterClockWise: '',
}, },
commandName: 'Helix', commandName: 'Helix',
}) })
@ -1179,6 +1191,295 @@ openSketch = startSketchOn(XY)
}) })
}) })
const helixCases = [
{
selectionType: 'segment',
testPoint: { x: 513, y: 221 },
expectedOutput: `helix001 = helix( axis = seg01, radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = seg01, radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
},
{
selectionType: 'sweepEdge',
testPoint: { x: 564, y: 364 },
expectedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
},
]
helixCases.map(
({ selectionType, testPoint, expectedOutput, expectedEditedOutput }) => {
test(`Helix point-and-click around ${selectionType}`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
page.on('console', console.log)
const initialCode = `sketch001 = startSketchOn('XZ')
profile001 = startProfileAt([0, 0], sketch001)
|> yLine(length = 100)
|> line(endAbsolute = [100, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(profile001, length = 100)`
// One dumb hardcoded screen pixel value
const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await test.step(`Go through the command bar flow`, async () => {
await toolbar.closePane('code')
await toolbar.helixButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
AngleStart: '',
Mode: '',
CounterClockWise: '',
Radius: '',
Revolutions: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
})
await cmdBar.selectOption({ name: 'Edge' }).click()
await clickOnEdge()
await cmdBar.progressCmdBar()
await cmdBar.argumentInput.focus()
await page.keyboard.insertText('20')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('0')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Mode: 'Edge',
Edge: `1 ${selectionType}`,
AngleStart: '0',
Revolutions: '20',
Radius: '1',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedOutput)
await toolbar.closePane('code')
})
await test.step(`Edit helix through the feature tree`, async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
await operationButton.dblclick()
const initialInput = '1'
const newInput = '5'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: initialInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
})
await page
.getByRole('button', { name: 'radius', exact: false })
.click()
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput
.locator('.cm-content')
.fill(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedEditedOutput)
await toolbar.closePane('code')
})
await test.step('Delete helix via feature tree selection', async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain(expectedEditedOutput)
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
})
})
}
)
test('Helix point-and-click on cylinder', async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn(XY)
profile001 = circle(
sketch001,
center = [0, 0],
radius = 100,
tag = $seg01,
)
extrude001 = extrude(profile001, length = 100)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 }
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const expectedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = false,)`
const expectedLine = `cylinder = extrude001,`
const expectedEditedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = true,)`
await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
Mode: '',
AngleStart: '',
Revolutions: '',
Radius: '',
CounterClockWise: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
})
await cmdBar.selectOption({ name: 'Cylinder' }).click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'cylinder',
currentArgValue: '',
headerArguments: {
Mode: 'Cylinder',
Cylinder: '',
AngleStart: '',
Revolutions: '',
CounterClockWise: '',
},
highlightedHeaderArg: 'cylinder',
commandName: 'Helix',
})
await clickOnWall()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Mode: 'Cylinder',
Cylinder: '1 face',
AngleStart: '360',
Revolutions: '1',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(expectedOutput)
await editor.expectState({
diagnostics: [],
activeLines: [expectedLine],
highlightedCode: '',
})
})
await test.step(`Edit helix through the feature tree`, async () => {
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.dblclick()
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
headerArguments: {
AngleStart: '360',
Revolutions: '1',
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
})
await cmdBar.selectOption({ name: 'True' }).click()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '360',
Revolutions: '1',
CounterClockWise: 'true',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedEditedOutput)
await editor.closePane()
})
await test.step('Delete helix via feature tree selection', async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
await editor.expectEditor.not.toContain(expectedEditedOutput)
})
})
const loftPointAndClickCases = [ const loftPointAndClickCases = [
{ shouldPreselect: true }, { shouldPreselect: true },
{ shouldPreselect: false }, { shouldPreselect: false },
@ -1340,9 +1641,10 @@ loft001 = loft([sketch001, sketch002])
{ {
targetType: 'circle', targetType: 'circle',
testPoint: { x: 700, y: 250 }, testPoint: { x: 700, y: 250 },
initialCode: `sketch001 = startSketchOn('YZ') initialCode: `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(YZ)
profile001 = circle(sketch001, center = [0, 0], radius = 500) profile001 = circle(sketch001, center = [0, 0], radius = 500)
sketch002 = startSketchOn('XZ') sketch002 = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> xLine(length = -500) |> xLine(length = -500)
|> tangentialArcTo([-2000, 500], %)`, |> tangentialArcTo([-2000, 500], %)`,
@ -1350,7 +1652,8 @@ sketch002 = startSketchOn('XZ')
{ {
targetType: 'rectangle', targetType: 'rectangle',
testPoint: { x: 710, y: 255 }, testPoint: { x: 710, y: 255 },
initialCode: `sketch001 = startSketchOn('YZ') initialCode: `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(YZ)
profile001 = startProfileAt([-400, -400], sketch001) profile001 = startProfileAt([-400, -400], sketch001)
|> angledLine([0, 800], %, $rectangleSegmentA001) |> angledLine([0, 800], %, $rectangleSegmentA001)
|> angledLine([ |> angledLine([
@ -1363,7 +1666,7 @@ profile001 = startProfileAt([-400, -400], sketch001)
], %) ], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
sketch002 = startSketchOn('XZ') sketch002 = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> xLine(length = -500) |> xLine(length = -500)
|> tangentialArcTo([-2000, 500], %)`, |> tangentialArcTo([-2000, 500], %)`,
@ -1507,7 +1810,8 @@ sketch002 = startSketchOn('XZ')
toolbar, toolbar,
cmdBar, cmdBar,
}) => { }) => {
const initialCode = `sketch001 = startSketchOn(YZ) const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(YZ)
|> circle( |> circle(
center = [0, 0], center = [0, 0],
radius = 500 radius = 500
@ -1616,7 +1920,7 @@ extrude001 = extrude(sketch001, length = -12)
const filletColor: [number, number, number] = [127, 127, 127] const filletColor: [number, number, number] = [127, 127, 127]
const backgroundColor: [number, number, number] = [30, 30, 30] const backgroundColor: [number, number, number] = [30, 30, 30]
const lowTolerance = 20 const lowTolerance = 20
const highTolerance = 40 const highTolerance = 70 // TODO: understand why I needed that for edgeColorYellow on macos (local)
// Setup // Setup
await test.step(`Initial test setup`, async () => { await test.step(`Initial test setup`, async () => {
@ -1703,6 +2007,54 @@ extrude001 = extrude(sketch001, length = -12)
await scene.expectPixelColor(filletColor, firstEdgeLocation, lowTolerance) await scene.expectPixelColor(filletColor, firstEdgeLocation, lowTolerance)
}) })
// Test 1.1: Edit fillet (segment type)
async function editFillet(
featureTreeIndex: number,
oldValue: string,
newValue: string
) {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Fillet',
featureTreeIndex
)
await operationButton.dblclick({ button: 'left' })
await cmdBar.expectState({
commandName: 'Fillet',
currentArgKey: 'radius',
currentArgValue: oldValue,
headerArguments: {
Radius: oldValue,
},
highlightedHeaderArg: 'radius',
stage: 'arguments',
})
await page.keyboard.insertText(newValue)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Radius: newValue,
},
commandName: 'Fillet',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
}
await test.step('Edit fillet via feature tree selection works', async () => {
const firstFilletFeatureTreeIndex = 0
const editedRadius = '1'
await editFillet(firstFilletFeatureTreeIndex, '5', editedRadius)
await editor.expectEditor.toContain(
firstFilletDeclaration.replace('radius = 5', 'radius = ' + editedRadius)
)
// Edit back to original radius
await editFillet(firstFilletFeatureTreeIndex, editedRadius, '5')
await editor.expectEditor.toContain(firstFilletDeclaration)
})
// Test 2: Command bar flow without preselected edges // Test 2: Command bar flow without preselected edges
await test.step(`Open fillet UI without selecting edges`, async () => { await test.step(`Open fillet UI without selecting edges`, async () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -1787,6 +2139,23 @@ extrude001 = extrude(sketch001, length = -12)
) )
}) })
// Test 2.1: Edit fillet (edgeSweep type)
await test.step('Edit fillet via feature tree selection works', async () => {
const secondFilletFeatureTreeIndex = 1
const editedRadius = '2'
await editFillet(secondFilletFeatureTreeIndex, '5', editedRadius)
await editor.expectEditor.toContain(
secondFilletDeclaration.replace(
'radius = 5',
'radius = ' + editedRadius
)
)
// Edit back to original radius
await editFillet(secondFilletFeatureTreeIndex, editedRadius, '5')
await editor.expectEditor.toContain(secondFilletDeclaration)
})
// Test 3: Delete fillets // Test 3: Delete fillets
await test.step('Delete fillet via feature tree selection', async () => { await test.step('Delete fillet via feature tree selection', async () => {
await test.step('Open Feature Tree Pane', async () => { await test.step('Open Feature Tree Pane', async () => {
@ -1809,6 +2178,43 @@ extrude001 = extrude(sketch001, length = -12)
}) })
}) })
test(`Fillet point-and-click edit rejected when not in pipe`, async ({
context,
page,
homePage,
scene,
toolbar,
}) => {
const initialCode = `sketch001 = startSketchOn(XY)
profile001 = circle(
sketch001,
center = [0, 0],
radius = 100,
tag = $seg01,
)
extrude001 = extrude(profile001, length = 100)
fillet001 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
await test.step('Double-click in feature tree and expect error toast', async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation('Fillet', 0)
await operationButton.dblclick({ button: 'left' })
await expect(
page.getByText(
'Only chamfer and fillet in pipe expressions are supported for edits'
)
).toBeVisible()
await page.waitForTimeout(1000)
})
})
test(`Fillet point-and-click delete`, async ({ test(`Fillet point-and-click delete`, async ({
context, context,
page, page,
@ -2074,7 +2480,8 @@ extrude001 = extrude(profile001, length = 5)
cmdBar, cmdBar,
}) => { }) => {
// Code samples // Code samples
const initialCode = `sketch001 = startSketchOn(XY) const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
|> startProfileAt([-12, -6], %) |> startProfileAt([-12, -6], %)
|> line(end = [0, 12]) |> line(end = [0, 12])
|> line(end = [24, 0]) |> line(end = [24, 0])
@ -2105,7 +2512,7 @@ extrude001 = extrude(sketch001, length = -12)
const chamferColor: [number, number, number] = [168, 168, 168] const chamferColor: [number, number, number] = [168, 168, 168]
const backgroundColor: [number, number, number] = [30, 30, 30] const backgroundColor: [number, number, number] = [30, 30, 30]
const lowTolerance = 20 const lowTolerance = 20
const highTolerance = 40 const highTolerance = 70 // TODO: understand why I needed that for edgeColorYellow on macos (local)
// Setup // Setup
await test.step(`Initial test setup`, async () => { await test.step(`Initial test setup`, async () => {
@ -2187,6 +2594,57 @@ extrude001 = extrude(sketch001, length = -12)
) )
}) })
// Test 1.1: Edit sweep
async function editChamfer(
featureTreeIndex: number,
oldValue: string,
newValue: string
) {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Chamfer',
featureTreeIndex
)
await operationButton.dblclick({ button: 'left' })
await cmdBar.expectState({
commandName: 'Chamfer',
currentArgKey: 'length',
currentArgValue: oldValue,
headerArguments: {
Length: oldValue,
},
highlightedHeaderArg: 'length',
stage: 'arguments',
})
await page.keyboard.insertText(newValue)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Length: newValue,
},
commandName: 'Chamfer',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
}
await test.step('Edit chamfer via feature tree selection works', async () => {
const firstChamferFeatureTreeIndex = 0
const editedLength = '1'
await editChamfer(firstChamferFeatureTreeIndex, '5', editedLength)
await editor.expectEditor.toContain(
firstChamferDeclaration.replace(
'length = 5',
'length = ' + editedLength
)
)
// Edit back to original radius
await editChamfer(firstChamferFeatureTreeIndex, editedLength, '5')
await editor.expectEditor.toContain(firstChamferDeclaration)
})
// Test 2: Command bar flow without preselected edges // Test 2: Command bar flow without preselected edges
await test.step(`Open chamfer UI without selecting edges`, async () => { await test.step(`Open chamfer UI without selecting edges`, async () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -2271,6 +2729,23 @@ extrude001 = extrude(sketch001, length = -12)
) )
}) })
// Test 2.1: Edit chamfer (edgeSweep type)
await test.step('Edit chamfer via feature tree selection works', async () => {
const secondChamferFeatureTreeIndex = 1
const editedLength = '2'
await editChamfer(secondChamferFeatureTreeIndex, '5', editedLength)
await editor.expectEditor.toContain(
secondChamferDeclaration.replace(
'length = 5',
'length = ' + editedLength
)
)
// Edit back to original length
await editChamfer(secondChamferFeatureTreeIndex, editedLength, '5')
await editor.expectEditor.toContain(secondChamferDeclaration)
})
// Test 3: Delete chamfer via feature tree selection // Test 3: Delete chamfer via feature tree selection
await test.step('Open Feature Tree Pane', async () => { await test.step('Open Feature Tree Pane', async () => {
await toolbar.openPane('feature-tree') await toolbar.openPane('feature-tree')
@ -2298,7 +2773,8 @@ extrude001 = extrude(sketch001, length = -12)
toolbar, toolbar,
}) => { }) => {
// Code samples // Code samples
const initialCode = `sketch001 = startSketchOn(XY) const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
|> startProfileAt([-12, -6], %) |> startProfileAt([-12, -6], %)
|> line(end = [0, 12]) |> line(end = [0, 12])
|> line(end = [24, 0], tag = $seg02) |> line(end = [24, 0], tag = $seg02)
@ -2452,7 +2928,8 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
toolbar, toolbar,
cmdBar, cmdBar,
}) => { }) => {
const initialCode = `sketch001 = startSketchOn(XZ) const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> circle(center = [0, 0], radius = 30) |> circle(center = [0, 0], radius = 30)
extrude001 = extrude(sketch001, length = 30) extrude001 = extrude(sketch001, length = 30)
` `
@ -2587,7 +3064,8 @@ extrude001 = extrude(sketch001, length = 30)
toolbar, toolbar,
cmdBar, cmdBar,
}) => { }) => {
const initialCode = `sketch001 = startSketchOn(XY) const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
|> startProfileAt([-20, 20], %) |> startProfileAt([-20, 20], %)
|> xLine(length = 40) |> xLine(length = 40)
|> yLine(length = -60) |> yLine(length = -60)
@ -2705,7 +3183,8 @@ extrude001 = extrude(sketch001, length = 40)
}) })
const shellSketchOnFacesCases = [ const shellSketchOnFacesCases = [
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> circle(center = [0, 0], radius = 100) |> circle(center = [0, 0], radius = 100)
|> extrude(length = 100) |> extrude(length = 100)
@ -2713,7 +3192,8 @@ sketch002 = startSketchOn(sketch001, 'END')
|> circle(center = [0, 0], radius = 50) |> circle(center = [0, 0], radius = 50)
|> extrude(length = 50) |> extrude(length = 50)
`, `,
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> circle(center = [0, 0], radius = 100) |> circle(center = [0, 0], radius = 100)
extrude001 = extrude(sketch001, length = 100) extrude001 = extrude(sketch001, length = 100)
@ -2996,6 +3476,39 @@ segAng(rectangleSegmentA002),
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = 'X')` const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = 'X')`
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy() expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
// Edit flow
const newAngle = '90'
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Revolve',
0
)
await operationButton.dblclick({ button: 'left' })
await cmdBar.expectState({
commandName: 'Revolve',
currentArgKey: 'angle',
currentArgValue: '360',
headerArguments: {
Angle: '360',
},
highlightedHeaderArg: 'angle',
stage: 'arguments',
})
await page.keyboard.insertText(newAngle)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Angle: newAngle,
},
commandName: 'Revolve',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await editor.expectEditor.toContain(
newCodeToFind.replace('angle = 360', 'angle = ' + newAngle)
)
}) })
test('revolve surface around edge from an extruded solid2d', async ({ test('revolve surface around edge from an extruded solid2d', async ({
context, context,
@ -3006,26 +3519,22 @@ segAng(rectangleSegmentA002),
toolbar, toolbar,
cmdBar, cmdBar,
}) => { }) => {
const initialCode = ` const initialCode = `sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ) |> startProfileAt([-102.57, 101.72], %)
|> startProfileAt([-102.57, 101.72], %) |> angledLine([0, 202.6], %, $rectangleSegmentA001)
|> angledLine([0, 202.6], %, $rectangleSegmentA001) |> angledLine([
|> angledLine([ segAng(rectangleSegmentA001) - 90,
segAng(rectangleSegmentA001) - 90, 202.6
202.6 ], %, $rectangleSegmentB001)
], %, $rectangleSegmentB001) |> angledLine([
|> angledLine([ segAng(rectangleSegmentA001),
segAng(rectangleSegmentA001), -segLen(rectangleSegmentA001)
-segLen(rectangleSegmentA001) ], %, $rectangleSegmentC001)
], %, $rectangleSegmentC001) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close()
|> close()
extrude001 = extrude(sketch001, length = 50) extrude001 = extrude(sketch001, length = 50)
sketch002 = startSketchOn(extrude001, rectangleSegmentA001) sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
|> circle( |> circle(center = [-11.34, 10.0], radius = 8.69)
center = [-11.34, 10.0],
radius = 8.69
)
` `
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
@ -3043,9 +3552,49 @@ radius = 8.69
const lineCodeToSelection = `|> angledLine([0, 202.6], %, $rectangleSegmentA001)` const lineCodeToSelection = `|> angledLine([0, 202.6], %, $rectangleSegmentA001)`
await page.getByText(lineCodeToSelection).click() await page.getByText(lineCodeToSelection).click()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = getOppositeEdge(rectangleSegmentA001)) ` const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = rectangleSegmentA001)`
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy() await editor.expectEditor.toContain(newCodeToFind)
// Edit flow
const newAngle = '180'
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Revolve',
0
)
await operationButton.dblclick({ button: 'left' })
await cmdBar.expectState({
commandName: 'Revolve',
currentArgKey: 'angle',
currentArgValue: '360',
headerArguments: {
Angle: '360',
},
highlightedHeaderArg: 'angle',
stage: 'arguments',
})
await page.keyboard.insertText(newAngle)
await page.getByRole('button', { name: 'Create new variable' }).click()
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
'angle001'
)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Angle: newAngle,
},
commandName: 'Revolve',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await editor.expectEditor.toContain('angle001 = ' + newAngle)
await editor.expectEditor.toContain(
newCodeToFind.replace('angle = 360', 'angle = angle001')
)
}) })
test('revolve sketch circle around line segment from startProfileAt sketch', async ({ test('revolve sketch circle around line segment from startProfileAt sketch', async ({
context, context,
@ -3056,26 +3605,22 @@ radius = 8.69
toolbar, toolbar,
cmdBar, cmdBar,
}) => { }) => {
const initialCode = ` const initialCode = `sketch002 = startSketchOn(XY)
sketch002 = startSketchOn(XY) |> startProfileAt([-2.02, 1.79], %)
|> startProfileAt([-2.02, 1.79], %) |> xLine(length = 2.6)
|> xLine(length = 2.6) sketch001 = startSketchOn(-XY)
sketch001 = startSketchOn('-XY') |> startProfileAt([-0.48, 1.25], %)
|> startProfileAt([-0.48, 1.25], %) |> angledLine([0, 2.38], %, $rectangleSegmentA001)
|> angledLine([0, 2.38], %, $rectangleSegmentA001) |> angledLine([segAng(rectangleSegmentA001) - 90, 2.4], %, $rectangleSegmentB001)
|> angledLine([segAng(rectangleSegmentA001) - 90, 2.4], %, $rectangleSegmentB001) |> angledLine([
|> angledLine([ segAng(rectangleSegmentA001),
segAng(rectangleSegmentA001), -segLen(rectangleSegmentA001)
-segLen(rectangleSegmentA001) ], %, $rectangleSegmentC001)
], %, $rectangleSegmentC001) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close()
|> close() extrude001 = extrude(sketch001, length = 5)
extrude001 = extrude(sketch001, length = 5) sketch003 = startSketchOn(extrude001, 'START')
sketch003 = startSketchOn(extrude001, 'START') |> circle(center = [-0.69, 0.56], radius = 0.28)
|> circle(
center = [-0.69, 0.56],
radius = 0.28
)
` `
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
@ -3094,9 +3639,44 @@ radius = 8.69
const lineCodeToSelection = `|> xLine(length = 2.6)` const lineCodeToSelection = `|> xLine(length = 2.6)`
await page.getByText(lineCodeToSelection).click() await page.getByText(lineCodeToSelection).click()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve(sketch003, angle = 360, axis = seg01)` const newCodeToFind = `revolve001 = revolve(sketch003, angle = 360, axis = seg01)`
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy() expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
// Edit flow
const newAngle = '270'
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Revolve',
0
)
await operationButton.dblclick({ button: 'left' })
await cmdBar.expectState({
commandName: 'Revolve',
currentArgKey: 'angle',
currentArgValue: '360',
headerArguments: {
Angle: '360',
},
highlightedHeaderArg: 'angle',
stage: 'arguments',
})
await page.keyboard.insertText(newAngle)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Angle: newAngle,
},
commandName: 'Revolve',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await editor.expectEditor.toContain(
newCodeToFind.replace('angle = 360', 'angle = ' + newAngle)
)
}) })
}) })
@ -3109,7 +3689,8 @@ radius = 8.69
toolbar, toolbar,
cmdBar, cmdBar,
}) => { }) => {
const initialCode = `sketch001 = startSketchOn(XZ) const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile001 = circle( profile001 = circle(
sketch001, sketch001,
center = [0, 0], center = [0, 0],

View File

@ -8,6 +8,7 @@ import {
createProject, createProject,
getPlaywrightDownloadDir, getPlaywrightDownloadDir,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
runningOnWindows,
} from './test-utils' } from './test-utils'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import fs from 'fs' import fs from 'fs'
@ -86,7 +87,7 @@ test(
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
}) })
@ -123,7 +124,7 @@ test(
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
const errorDir = path.join(dir, 'broken-code') const errorDir = path.join(dir, 'broken-code')
@ -161,7 +162,7 @@ test(
// gray at this pixel means the stream has loaded in the most // gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color) // user way we can verify it (pixel color)
await expect await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { .poll(() => u.getGreatestPixDiff(pointOnModel, [110, 110, 110]), {
timeout: 10_000, timeout: 10_000,
}) })
.toBeLessThan(20) .toBeLessThan(20)
@ -191,7 +192,7 @@ test(
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator` const crypticErrorText = `The arg tag was given, but it was the wrong type`
await expect(page.getByText(crypticErrorText).first()).toBeVisible() await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared. // black pixel means the scene has been cleared.
@ -212,7 +213,7 @@ test(
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
const emptyDir = path.join(dir, 'empty') const emptyDir = path.join(dir, 'empty')
@ -247,7 +248,7 @@ test(
// gray at this pixel means the stream has loaded in the most // gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color) // user way we can verify it (pixel color)
await expect await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { .poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
timeout: 10_000, timeout: 10_000,
}) })
.toBeLessThan(15) .toBeLessThan(15)
@ -289,7 +290,7 @@ test(
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
@ -318,7 +319,7 @@ test(
// gray at this pixel means the stream has loaded in the most // gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color) // user way we can verify it (pixel color)
await expect await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { .poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
timeout: 10_000, timeout: 10_000,
}) })
.toBeLessThan(15) .toBeLessThan(15)
@ -351,11 +352,14 @@ test(
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene', 'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
if (runningOnWindows()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
await fsp.copyFile( await fsp.copyFile(
@ -389,7 +393,7 @@ test(
// gray at this pixel means the stream has loaded in the most // gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color) // user way we can verify it (pixel color)
await expect await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { .poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
timeout: 10_000, timeout: 10_000,
}) })
.toBeLessThan(15) .toBeLessThan(15)
@ -409,7 +413,7 @@ test(
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator` const crypticErrorText = `The arg tag was given, but it was the wrong type`
await expect(page.getByText(crypticErrorText).first()).toBeVisible() await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared. // black pixel means the scene has been cleared.
@ -439,7 +443,6 @@ test(
await page.getByText('broken-code').click() await page.getByText('broken-code').click()
// Gotcha: You can not use scene.waitForExecutionDone() since the KCL code is going to fail // Gotcha: You can not use scene.waitForExecutionDone() since the KCL code is going to fail
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({ await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000, timeout: 20_000,
}) })
@ -453,7 +456,7 @@ test(
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator` const crypticErrorText = `The arg tag was given, but it was the wrong type`
await expect(page.getByText(crypticErrorText).first()).toBeVisible() await expect(page.getByText(crypticErrorText).first()).toBeVisible()
} }
) )
@ -469,12 +472,15 @@ test.describe('Can export from electron app', () => {
if (!tronApp) { if (!tronApp) {
fail() fail()
} }
if (runningOnWindows()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
}) })
@ -506,7 +512,7 @@ test.describe('Can export from electron app', () => {
// gray at this pixel means the stream has loaded in the most // gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color) // user way we can verify it (pixel color)
await expect await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), { .poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
timeout: 10_000, timeout: 10_000,
}) })
.toBeLessThan(15) .toBeLessThan(15)
@ -547,7 +553,7 @@ test.describe('Can export from electron app', () => {
}, },
{ timeout: 15_000 } { timeout: 15_000 }
) )
.toBeGreaterThan(300_000) .toBeGreaterThan(50_000)
// clean up exported file // clean up exported file
await fsp.rm(filepath) await fsp.rm(filepath)
@ -1328,6 +1334,9 @@ test(
'Can load a file with CRLF line endings', 'Can load a file with CRLF line endings',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
if (runningOnWindows()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const routerTemplateDir = path.join(dir, 'router-template-slate') const routerTemplateDir = path.join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true }) await fsp.mkdir(routerTemplateDir, { recursive: true })
@ -1497,7 +1506,12 @@ test(
await u.waitForPageLoad() await u.waitForPageLoad()
await page.locator('.cm-content').fill(`sketch001 = startSketchOn(XZ) // The file should be prepopulated with the user's unit settings.
await expect(page.locator('.cm-content')).toHaveText(
'@settings(defaultLengthUnit = in)'
)
await page.locator('.cm-content').fill(`sketch001 = startSketchOn('XZ')
|> startProfileAt([-87.4, 282.92], %) |> startProfileAt([-87.4, 282.92], %)
|> line(end = [324.07, 27.199], tag = $seg01) |> line(end = [324.07, 27.199], tag = $seg01)
|> line(end = [118.328, -291.754]) |> line(end = [118.328, -291.754])

View File

@ -4,9 +4,9 @@ import path from 'path'
import * as fsp from 'fs/promises' import * as fsp from 'fs/promises'
import { import {
getUtils, getUtils,
executorInputPath,
TEST_COLORS, TEST_COLORS,
TestColor, TestColor,
executorInputPath,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
} from './test-utils' } from './test-utils'
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates' import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
@ -331,7 +331,7 @@ extrude001 = extrude(sketch001, length = 50)
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`@settings(defaultLengthUnit = mm) `@settings(defaultLengthUnit = mm)
sketch002 = startSketchOn('XY') sketch002 = startSketchOn(XY)
profile002 = startProfileAt([72.24, -52.05], sketch002) profile002 = startProfileAt([72.24, -52.05], sketch002)
|> angledLine([0, 181.26], %, $rectangleSegmentA001) |> angledLine([0, 181.26], %, $rectangleSegmentA001)
|> angledLine([ |> angledLine([
@ -582,7 +582,7 @@ extrude002 = extrude(profile002, length = 150)
const bracketDir = path.join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
}) })
@ -619,6 +619,7 @@ extrude002 = extrude(profile002, length = 150)
test(`View gizmo stays visible even when zoomed out all the way`, async ({ test(`View gizmo stays visible even when zoomed out all the way`, async ({
page, page,
homePage, homePage,
scene,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -632,7 +633,7 @@ extrude002 = extrude(profile002, length = 150)
await test.step(`Load an empty file`, async () => { await test.step(`Load an empty file`, async () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem('persistCode', '') localStorage.setItem('persistCode', '@settings(defaultLengthUnit = in)')
}) })
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
@ -646,22 +647,31 @@ extrude002 = extrude(profile002, length = 150)
timeout: 5000, timeout: 5000,
message: 'Plane color is visible', message: 'Plane color is visible',
}) })
.toBeLessThanOrEqual(15) .toBeLessThanOrEqual(20)
await expect(scene.startEditSketchBtn).toBeEnabled()
let maxZoomOuts = 10 let maxZoomOuts = 10
let middlePixelIsBackgroundColor = let middlePixelIsBackgroundColor =
(await middlePixelIsColor(bgColor)) < 10 (await middlePixelIsColor(bgColor)) < 10
console.time('pressing control')
await page.keyboard.down('Control')
while (!middlePixelIsBackgroundColor && maxZoomOuts > 0) { while (!middlePixelIsBackgroundColor && maxZoomOuts > 0) {
await page.keyboard.down('Control')
await page.mouse.move(600, 460)
await page.mouse.down({ button: 'right' })
await page.mouse.move(600, 50, { steps: 20 })
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Control')
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.move(650, 460)
console.time('moved to start point')
await page.mouse.down({ button: 'right' })
console.time('moused down')
await page.mouse.move(650, 50, { steps: 20 })
console.time('moved to end point')
await page.waitForTimeout(100)
await page.mouse.up({ button: 'right' })
console.time('moused up')
maxZoomOuts-- maxZoomOuts--
middlePixelIsBackgroundColor = (await middlePixelIsColor(bgColor)) < 10 middlePixelIsBackgroundColor = (await middlePixelIsColor(bgColor)) < 15
} }
await page.keyboard.up('Control')
expect(middlePixelIsBackgroundColor, { expect(middlePixelIsBackgroundColor, {
message: 'We should not see the default planes', message: 'We should not see the default planes',
@ -678,13 +688,12 @@ extrude002 = extrude(profile002, length = 150)
homePage, homePage,
scene, scene,
toolbar, toolbar,
viewport,
}) => { }) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const legoDir = path.join(dir, 'lego') const legoDir = path.join(dir, 'lego')
await fsp.mkdir(legoDir, { recursive: true }) await fsp.mkdir(legoDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('lego.kcl'), executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
path.join(legoDir, 'main.kcl') path.join(legoDir, 'main.kcl')
) )
}) })
@ -697,11 +706,8 @@ extrude002 = extrude(profile002, length = 150)
await scene.loadingIndicator.waitFor({ state: 'detached' }) await scene.loadingIndicator.waitFor({ state: 'detached' })
}) })
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => { await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
await scene.expectPixelColor( // TODO: use the viewport size to pick the center point, but the `viewport` fixture's values were wrong.
[143, 143, 143], await scene.expectPixelColor([116, 116, 116], { x: 500, y: 250 }, 15)
{ x: (viewport?.width ?? 1200) / 2, y: (viewport?.height ?? 500) / 2 },
15
)
}) })
}) })
@ -778,6 +784,19 @@ plane002 = offsetPlane(XZ, offset = -2 * x)`
await editor.expectEditor.not.toContain(`plane002`) await editor.expectEditor.not.toContain(`plane002`)
}) })
}) })
test.fail(
'Console errors cause tests to fail',
async ({ page, homePage }) => {
const u = await getUtils(page)
await homePage.goToModelingScene()
await u.openAndClearDebugPanel()
await page.getByTestId('custom-cmd-input').fill('foobar')
await page.getByTestId('custom-cmd-send-button').scrollIntoViewIfNeeded()
await page.getByTestId('custom-cmd-send-button').click()
}
)
}) })
async function clickExportButton(page: Page) { async function clickExportButton(page: Page) {

View File

@ -113,7 +113,8 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([2.61, -4.01], %) |> startProfileAt([2.61, -4.01], %)
|> xLine(length = 8.73) |> xLine(length = 8.73)
|> tangentialArcTo([8.33, -1.31], %)` |> tangentialArcTo([8.33, -1.31], %)`
@ -159,7 +160,10 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect.poll(u.normalisedEditorCode, { timeout: 1000 }) await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
.toBe(`sketch002 = startSketchOn(XZ) .toBe(`@settings(defaultLengthUnit = in)
sketch002 = startSketchOn(XZ)
sketch001 = startProfileAt([12.34, -12.34], sketch002) sketch001 = startProfileAt([12.34, -12.34], sketch002)
|> yLine(length = 12.34) |> yLine(length = 12.34)
@ -789,7 +793,8 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
200 200
) )
let codeStr = 'sketch001 = startSketchOn(XY)' let codeStr =
'@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XY)'
await page.mouse.click(center.x, viewportSize.height * 0.55) await page.mouse.click(center.x, viewportSize.height * 0.55)
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
@ -868,7 +873,8 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
await u.openDebugPanel() await u.openDebugPanel()
const code = `sketch001 = startSketchOn(-XZ) const code = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(-XZ)
profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff( profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
scale * 34.8 scale * 34.8
)}], sketch001) )}], sketch001)
@ -898,7 +904,7 @@ profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
await page.mouse.move(700, 200, { steps: 10 }) await page.mouse.move(700, 200, { steps: 10 })
await page.mouse.click(700, 200, { delay: 200 }) await page.mouse.click(700, 200, { delay: 200 })
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn(-XZ)` `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(-XZ)`
) )
let prevContent = await page.locator('.cm-content').innerText() let prevContent = await page.locator('.cm-content').innerText()
@ -1426,7 +1432,8 @@ test.describe(`Sketching with offset planes`, () => {
await context.addInitScript(() => { await context.addInitScript(() => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`offsetPlane001 = offsetPlane(XY, offset = 10)` `@settings(defaultLengthUnit = in)
offsetPlane001 = offsetPlane(XY, offset = 10)`
) )
}) })
@ -1440,7 +1447,7 @@ test.describe(`Sketching with offset planes`, () => {
await test.step(`Hovering should highlight code`, async () => { await test.step(`Hovering should highlight code`, async () => {
await planeHover() await planeHover()
await editor.expectState({ await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane(XY,offset=10)`], activeLines: [`@settings(defaultLengthUnit = in)`],
diagnostics: [], diagnostics: [],
highlightedCode: 'offsetPlane(XY, offset = 10)', highlightedCode: 'offsetPlane(XY, offset = 10)',
}) })
@ -1453,7 +1460,7 @@ test.describe(`Sketching with offset planes`, () => {
await expect(toolbar.lineBtn).toBeEnabled() await expect(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)') await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
await editor.expectState({ await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane(XY,offset=10)`], activeLines: [`@settings(defaultLengthUnit = in)`],
diagnostics: [], diagnostics: [],
highlightedCode: '', highlightedCode: '',
}) })
@ -1604,7 +1611,8 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
await context.addInitScript(() => { await context.addInitScript(() => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile002 = startProfileAt([40.68, 87.67], sketch001) profile002 = startProfileAt([40.68, 87.67], sketch001)
|> xLine(length = 239.17) |> xLine(length = 239.17)
profile003 = startProfileAt([206.63, -56.73], sketch001) profile003 = startProfileAt([206.63, -56.73], sketch001)
@ -2172,7 +2180,8 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([6.24, 4.54], sketch001) profile001 = startProfileAt([6.24, 4.54], sketch001)
|> line(end = [-0.41, 6.99]) |> line(end = [-0.41, 6.99])
|> line(end = [8.61, 0.74]) |> line(end = [8.61, 0.74])
@ -2317,7 +2326,8 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([6.24, 4.54], sketch001) profile001 = startProfileAt([6.24, 4.54], sketch001)
|> line(end = [-0.41, 6.99]) |> line(end = [-0.41, 6.99])
|> line(end = [8.61, 0.74]) |> line(end = [8.61, 0.74])
@ -2422,7 +2432,8 @@ profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([-63.43, 193.08], sketch001) profile001 = startProfileAt([-63.43, 193.08], sketch001)
|> line(end = [168.52, 149.87]) |> line(end = [168.52, 149.87])
|> line(end = [190.29, -39.18]) |> line(end = [190.29, -39.18])
@ -2486,7 +2497,11 @@ extrude001 = extrude(profile003, length = 5)
page, page,
}) => { }) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem('persistCode', `myVar = 5`) localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
myVar = 5`
)
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
@ -2533,7 +2548,8 @@ extrude001 = extrude(profile003, length = 5)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([85.19, 338.59], sketch001) profile001 = startProfileAt([85.19, 338.59], sketch001)
|> line(end = [213.3, -94.52]) |> line(end = [213.3, -94.52])
|> line(end = [-230.09, -55.34]) |> line(end = [-230.09, -55.34])
@ -2575,7 +2591,8 @@ profile002 = startProfileAt([85.81, 52.55], sketch002)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`thePart = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
thePart = startSketchOn(XZ)
|> startProfileAt([7.53, 10.51], %) |> startProfileAt([7.53, 10.51], %)
|> line(end = [12.54, 1.83]) |> line(end = [12.54, 1.83])
|> line(end = [6.65, -6.91]) |> line(end = [6.65, -6.91])
@ -2636,7 +2653,8 @@ extrude001 = extrude(thePart, length = 75)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([6.71, -3.66], sketch001) profile001 = startProfileAt([6.71, -3.66], sketch001)
|> line(end = [2.65, 9.02], tag = $seg02) |> line(end = [2.65, 9.02], tag = $seg02)
|> line(end = [3.73, -9.36], tag = $seg01) |> line(end = [3.73, -9.36], tag = $seg01)
@ -2809,7 +2827,8 @@ extrude003 = extrude(profile011, length = 2.5)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([34, 42.66], sketch001) profile001 = startProfileAt([34, 42.66], sketch001)
|> line(end = [102.65, 151.99]) |> line(end = [102.65, 151.99])
|> line(end = [76, -138.66]) |> line(end = [76, -138.66])

View File

@ -76,11 +76,11 @@ part001 = startSketchOn(-XZ)
|> xLine(endAbsolute = totalLen, tag = $seg03) |> xLine(endAbsolute = totalLen, tag = $seg03)
|> yLine(length = -armThick, tag = $seg01) |> yLine(length = -armThick, tag = $seg01)
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle = HALF_TURN, angle = turns::HALF_TURN,
offset = -armThick, offset = -armThick,
intersectTag = seg04 intersectTag = seg04
}, %) }, %)
|> angledLineToY([segAng(seg04, %) + 180, ZERO], %) |> angledLineToY([segAng(seg04, %) + 180, turns::ZERO], %)
|> angledLineToY({ |> angledLineToY({
angle = -bottomAng, angle = -bottomAng,
to = -totalHeightHalf - armThick, to = -totalHeightHalf - armThick,
@ -88,12 +88,12 @@ part001 = startSketchOn(-XZ)
|> xLine(length = endAbsolute = segEndX(seg03) + 0) |> xLine(length = endAbsolute = segEndX(seg03) + 0)
|> yLine(length = -segLen(seg01, %)) |> yLine(length = -segLen(seg01, %))
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle = HALF_TURN, angle = turns::HALF_TURN,
offset = -armThick, offset = -armThick,
intersectTag = seg02 intersectTag = seg02
}, %) }, %)
|> angledLineToY([segAng(seg02, %) + 180, -baseHeight], %) |> angledLineToY([segAng(seg02, %) + 180, -baseHeight], %)
|> xLine(endAbsolute = ZERO) |> xLine(endAbsolute = turns::ZERO)
|> close() |> close()
|> extrude(length = 4)` |> extrude(length = 4)`
) )

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 51 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: 45 KiB

After

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

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 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: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -1,5 +1,5 @@
{ {
"original_source_code": "sketch001 = startSketchOn('XZ')\nprofile001 = startProfileAt([57.81, 250.51], sketch001)\n |> line(end = [121.13, 56.63], tag = $seg02)\n |> line(end = [83.37, -34.61], tag = $seg01)\n |> line(end = [19.66, -116.4])\n |> line(end = [-221.8, -41.69])\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude001 = extrude(profile001, length = 200)\nsketch002 = startSketchOn('XZ')\n |> startProfileAt([-73.64, -42.89], %)\n |> xLine(length = 173.71)\n |> line(end = [-22.12, -94.4])\n |> xLine(length = -156.98)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude002 = extrude(sketch002, length = 50)\nsketch003 = startSketchOn('XY')\n |> startProfileAt([52.92, 157.81], %)\n |> angledLine([0, 176.4], %, $rectangleSegmentA001)\n |> angledLine([\n segAng(rectangleSegmentA001) - 90,\n 53.4\n ], %, $rectangleSegmentB001)\n |> angledLine([\n segAng(rectangleSegmentA001),\n -segLen(rectangleSegmentA001)\n ], %, $rectangleSegmentC001)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude003 = extrude(sketch003, length = 20)\n", "original_source_code": "sketch001 = startSketchOn('XZ')\nprofile001 = startProfileAt([57.81, 250.51], sketch001)\n |> line(end = [121.13, 56.63], tag = $seg02)\n |> line(end = [83.37, -34.61], tag = $seg01)\n |> line(end = [19.66, -116.4])\n |> line(end = [-221.8, -41.69])\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude001 = extrude(profile001, length = 200)\nsketch002 = startSketchOn('XZ')\n |> startProfileAt([-73.64, -42.89], %)\n |> xLine(length = 173.71)\n |> line(end = [-22.12, -94.4])\n |> xLine(length = -156.98)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude002 = extrude(sketch002, length = 50)\nsketch003 = startSketchOn(XY)\n |> startProfileAt([52.92, 157.81], %)\n |> angledLine([0, 176.4], %, $rectangleSegmentA001)\n |> angledLine([\n segAng(rectangleSegmentA001) - 90,\n 53.4\n ], %, $rectangleSegmentB001)\n |> angledLine([\n segAng(rectangleSegmentA001),\n -segLen(rectangleSegmentA001)\n ], %, $rectangleSegmentC001)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude003 = extrude(sketch003, length = 20)\n",
"prompt": "make this neon green please, use #39FF14", "prompt": "make this neon green please, use #39FF14",
"source_ranges": [ "source_ranges": [
{ {

View File

@ -29,5 +29,5 @@
} }
} }
], ],
"kcl_version": "0.2.52" "kcl_version": "0.2.54"
} }

View File

@ -0,0 +1,99 @@
import {
runningOnLinux,
runningOnMac,
runningOnWindows,
orRunWhenFullSuiteEnabled,
} from './test-utils'
describe('platform detection utilities', () => {
const originalPlatform = process.platform
afterAll(() => {
Object.defineProperty(process, 'platform', {
value: originalPlatform,
})
})
describe('runningOnLinux', () => {
it('returns true on Linux', () => {
Object.defineProperty(process, 'platform', {
value: 'linux',
})
expect(runningOnLinux()).toBe(true)
})
it('returns false on other platforms', () => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
})
expect(runningOnLinux()).toBe(false)
})
})
describe('runningOnMac', () => {
it('returns true on Mac', () => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
})
expect(runningOnMac()).toBe(true)
})
it('returns false on other platforms', () => {
Object.defineProperty(process, 'platform', {
value: 'linux',
})
expect(runningOnMac()).toBe(false)
})
})
describe('runningOnWindows', () => {
it('returns true on Windows', () => {
Object.defineProperty(process, 'platform', {
value: 'win32',
})
expect(runningOnWindows()).toBe(true)
})
it('returns false on other platforms', () => {
Object.defineProperty(process, 'platform', {
value: 'linux',
})
expect(runningOnWindows()).toBe(false)
})
})
})
describe('utility to bypass unreliable tests', () => {
const originalEnv = { ...process.env }
afterAll(() => {
process.env = { ...originalEnv }
})
it('always runs them on dedicated branch', () => {
process.env.GITHUB_EVENT_NAME = 'push'
process.env.GITHUB_REF = 'refs/heads/all-e2e'
process.env.GITHUB_HEAD_REF = ''
process.env.GITHUB_BASE_REF = ''
const shouldSkip = orRunWhenFullSuiteEnabled()
expect(shouldSkip).toBe(false)
})
it('skips them on the main branch', () => {
process.env.GITHUB_EVENT_NAME = 'push'
process.env.GITHUB_REF = 'refs/heads/main'
process.env.GITHUB_HEAD_REF = ''
process.env.GITHUB_BASE_REF = ''
const shouldSkip = orRunWhenFullSuiteEnabled()
expect(shouldSkip).toBe(true)
})
it('skips them on pull requests', () => {
process.env.GITHUB_EVENT_NAME = 'pull_request'
process.env.GITHUB_REF = 'refs/pull/5883/merge'
process.env.GITHUB_HEAD_REF = 'my-branch'
process.env.GITHUB_BASE_REF = 'main'
const shouldSkip = orRunWhenFullSuiteEnabled()
expect(shouldSkip).toBe(true)
})
})

View File

@ -26,6 +26,7 @@ import { isArray } from 'lib/utils'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { DeepPartial } from 'lib/types' import { DeepPartial } from 'lib/types'
import { Configuration } from 'lang/wasm' import { Configuration } from 'lang/wasm'
import { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
const toNormalizedCode = (text: string) => { const toNormalizedCode = (text: string) => {
return text.replace(/\s+/g, '') return text.replace(/\s+/g, '')
@ -55,8 +56,21 @@ export const commonPoints = {
export const editorSelector = '[role="textbox"][data-language="kcl"]' export const editorSelector = '[role="textbox"][data-language="kcl"]'
type PaneId = 'variables' | 'code' | 'files' | 'logs' type PaneId = 'variables' | 'code' | 'files' | 'logs'
export function runningOnLinux() {
return process.platform === 'linux'
}
export function runningOnMac() {
return process.platform === 'darwin'
}
export function runningOnWindows() {
return process.platform === 'win32'
}
export function orRunWhenFullSuiteEnabled() { export function orRunWhenFullSuiteEnabled() {
return process.env.GITHUB_REF !== 'all-e2e' const branch = process.env.GITHUB_REF?.replace('refs/heads/', '')
return branch !== 'all-e2e'
} }
async function waitForPageLoadWithRetry(page: Page) { async function waitForPageLoadWithRetry(page: Page) {
@ -748,7 +762,7 @@ export interface Paths {
} }
export const doExport = async ( export const doExport = async (
output: Models['OutputFormat_type'], output: Models['OutputFormat3d_type'],
rootDir: string, rootDir: string,
page: Page, page: Page,
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown' exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
@ -921,47 +935,39 @@ export async function setup(
} }
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) { function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
// enabled for chrome for now page.on('pageerror', (exception: any) => {
if (page.context().browser()?.browserType().name() === 'chromium') { if (isErrorWhitelisted(exception)) {
// No idea wtf exception is return
page.on('pageerror', (exception: any) => { }
if (isErrorWhitelisted(exception)) { // Only disable this environment variable if you want to collect console errors
return if (process.env.FAIL_ON_CONSOLE_ERRORS !== 'false') {
} // Use expect to prevent page from closing and not cleaning up
expect(`An error was detected in the console: \r\n message:${exception.message} \r\n name:${exception.name} \r\n stack:${exception.stack}
// only set this env var to false if you want to collect console errors *Either fix the console error or add it to the whitelist defined in ./lib/console-error-whitelist.ts (if the error can be safely ignored)
// This can be configured in the GH workflow. This should be set to true by default (we want tests to fail when `).toEqual('Console error detected')
// unwhitelisted console errors are detected). } else {
if (process.env.FAIL_ON_CONSOLE_ERRORS === 'true') { // Add errors to `test-results/exceptions.txt` as a test artifact
// Fail when running on CI and FAIL_ON_CONSOLE_ERRORS is set fsp
// use expect to prevent page from closing and not cleaning up .appendFile(
expect(`An error was detected in the console: \r\n message:${exception.message} \r\n name:${exception.name} \r\n stack:${exception.stack} './test-results/exceptions.txt',
[
*Either fix the console error or add it to the whitelist defined in ./lib/console-error-whitelist.ts (if the error can be safely ignored) '~~~',
`).toEqual('Console error detected') `triggered_by_test:${
} else { testInfo?.file + ' ' + (testInfo?.title || ' ')
// the (test-results/exceptions.txt) file will be uploaded as part of an upload artifact in GH }`,
fsp `name:${exception.name}`,
.appendFile( `message:${exception.message}`,
'./test-results/exceptions.txt', `stack:${exception.stack}`,
[ `project:${testInfo?.project.name}`,
'~~~', '~~~',
`triggered_by_test:${ ].join('\n')
testInfo?.file + ' ' + (testInfo?.title || ' ') )
}`, .catch((err) => {
`name:${exception.name}`, console.error(err)
`message:${exception.message}`, })
`stack:${exception.stack}`, }
`project:${testInfo?.project.name}`, })
'~~~',
].join('\n')
)
.catch((err) => {
console.error(err)
})
}
})
}
} }
export async function isOutOfViewInScrollContainer( export async function isOutOfViewInScrollContainer(
element: Locator, element: Locator,
@ -1112,3 +1118,15 @@ export function settingsToToml(settings: DeepPartial<Configuration>) {
export function tomlToSettings(toml: string): DeepPartial<Configuration> { export function tomlToSettings(toml: string): DeepPartial<Configuration> {
return TOML.parse(toml) return TOML.parse(toml)
} }
export function tomlToPerProjectSettings(
toml: string
): DeepPartial<ProjectConfiguration> {
return TOML.parse(toml)
}
export function perProjectsettingsToToml(
settings: DeepPartial<ProjectConfiguration>
) {
return TOML.stringify(settings as any)
}

View File

@ -4,7 +4,6 @@ import {
getUtils, getUtils,
TEST_COLORS, TEST_COLORS,
pollEditorLinesSelectedLength, pollEditorLinesSelectedLength,
executorInputPath,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
} from './test-utils' } from './test-utils'
import { XOR } from 'lib/utils' import { XOR } from 'lib/utils'
@ -81,7 +80,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 79 `@settings(defaultLengthUnit = in)
yo = 79
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4], tag = $seg01) |> line(end = [74.36, 130.4], tag = $seg01)
@ -145,7 +145,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 5 `@settings(defaultLengthUnit = in)
yo = 5
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4], tag = $seg01) |> line(end = [74.36, 130.4], tag = $seg01)
@ -159,31 +160,6 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|> xLine(length = segLen(seg_what)) |> xLine(length = segLen(seg_what))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])` |> line(endAbsolute = [profileStartX(%), profileStartY(%)])`
) )
const isChecked = await createNewVariableCheckbox.isChecked()
const addVariable = testName === 'Add variable'
XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct
(await createNewVariableCheckbox.click())
await page
.getByRole('button', { name: 'Add constraining value' })
.click()
// Wait for the codemod to take effect
await expect(page.locator('.cm-content')).toContainText(`angle: -57,`)
await expect(page.locator('.cm-content')).toContainText(
`offset: ${offset},`
)
await pollEditorLinesSelectedLength(page, 2)
const activeLinesContent = await page.locator('.cm-activeLine').all()
await expect(activeLinesContent[0]).toHaveText(
`|> line(end = [74.36, 130.4], tag = $seg01)`
)
await expect(activeLinesContent[1]).toHaveText(`}, %)`)
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
}) })
const u = await getUtils(page) const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -277,7 +253,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 5 `@settings(defaultLengthUnit = in)
yo = 5
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4]) |> line(end = [74.36, 130.4])
@ -387,7 +364,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 5 `@settings(defaultLengthUnit = in)
yo = 5
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4]) |> line(end = [74.36, 130.4])
@ -486,13 +464,13 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
testName: 'Add variable, selecting axis', testName: 'Add variable, selecting axis',
addVariable: true, addVariable: true,
axisSelect: true, axisSelect: true,
value: 'QUARTER_TURN - angle001', value: 'turns::QUARTER_TURN - angle001',
}, },
{ {
testName: 'No variable, selecting axis', testName: 'No variable, selecting axis',
addVariable: false, addVariable: false,
axisSelect: true, axisSelect: true,
value: 'QUARTER_TURN - 7', value: 'turns::QUARTER_TURN - 7',
}, },
] as const ] as const
for (const { testName, addVariable, value, axisSelect } of cases) { for (const { testName, addVariable, value, axisSelect } of cases) {
@ -500,7 +478,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 5 `@settings(defaultLengthUnit = in)
yo = 5
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4]) |> line(end = [74.36, 130.4])
@ -602,7 +581,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 5 `@settings(defaultLengthUnit = in)
yo = 5
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4]) |> line(end = [74.36, 130.4])
@ -688,7 +668,8 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 5 `@settings(defaultLengthUnit = in)
yo = 5
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4]) |> line(end = [74.36, 130.4])
@ -768,7 +749,8 @@ part002 = startSketchOn(XZ)
await page.addInitScript(async (customCode) => { await page.addInitScript(async (customCode) => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 5 `@settings(defaultLengthUnit = in)
yo = 5
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4]) |> line(end = [74.36, 130.4])
@ -869,7 +851,8 @@ part002 = startSketchOn(XZ)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 5 `@settings(defaultLengthUnit = in)
yo = 5
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4]) |> line(end = [74.36, 130.4])
@ -935,12 +918,12 @@ part002 = startSketchOn(XZ)
test.describe('Axis & segment - no modal constraints', () => { test.describe('Axis & segment - no modal constraints', () => {
const cases = [ const cases = [
{ {
codeAfter: `|> line(endAbsolute = [154.9, ZERO])`, codeAfter: `|> line(endAbsolute = [154.9, turns::ZERO])`,
axisClick: { x: 950, y: 250 }, axisClick: { x: 950, y: 250 },
constraintName: 'Snap To X', constraintName: 'Snap To X',
}, },
{ {
codeAfter: `|> line(endAbsolute = [ZERO, 61.34])`, codeAfter: `|> line(endAbsolute = [turns::ZERO, 61.34])`,
axisClick: { x: 600, y: 150 }, axisClick: { x: 600, y: 150 },
constraintName: 'Snap To Y', constraintName: 'Snap To Y',
}, },
@ -950,7 +933,8 @@ part002 = startSketchOn(XZ)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 5 `@settings(defaultLengthUnit = in)
yo = 5
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4]) |> line(end = [74.36, 130.4])
@ -1117,9 +1101,19 @@ test.describe('Electron constraint tests', () => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'test-sample') const bracketDir = path.join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.writeFile(
executorInputPath('angled_line.kcl'), path.join(bracketDir, 'main.kcl'),
path.join(bracketDir, 'main.kcl') `@settings(defaultLengthUnit = in)
const part001 = startSketchOn(XY)
|> startProfileAt([4.83, 12.56], %)
|> line(end = [15.1, 2.48])
|> line(end = [3.15, -9.85], tag = $seg01)
|> line(end = [-15.17, -4.1])
|> angledLine([segAng(seg01), 12.35], %)
|> line(end = [-13.02, 10.03])
|> close()
|> extrude(length = 4)`,
'utf-8'
) )
}) })

View File

@ -255,7 +255,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
await context.addInitScript(() => { await context.addInitScript(() => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
` `@settings(defaultLengthUnit = in)
const sketch002 = startSketchOn(XZ) const sketch002 = startSketchOn(XZ)
|> startProfileAt([-108.83, -57.48], %) |> startProfileAt([-108.83, -57.48], %)
|> angledLine([0, 105.13], %, $rectangleSegmentA001) |> angledLine([0, 105.13], %, $rectangleSegmentA001)

View File

@ -46,7 +46,7 @@ test.describe('Testing in-app sample loading', () => {
page.getByRole('option', { page.getByRole('option', {
name, name,
}) })
const warningText = page.getByText('Overwrite current file and units?') const warningText = page.getByText('Overwrite current file with sample?')
const confirmButton = page.getByRole('button', { name: 'Submit command' }) const confirmButton = page.getByRole('button', { name: 'Submit command' })
await test.step(`Precondition: check the initial code`, async () => { await test.step(`Precondition: check the initial code`, async () => {
@ -110,11 +110,9 @@ test.describe('Testing in-app sample loading', () => {
const commandMethodOption = page.getByRole('option', { const commandMethodOption = page.getByRole('option', {
name: 'Overwrite', name: 'Overwrite',
}) })
const newFileWarning = page.getByText( const newFileWarning = page.getByText('Create a new file from sample?')
'Create a new file, overwrite project units?'
)
const overwriteWarning = page.getByText( const overwriteWarning = page.getByText(
'Overwrite current file and units?' 'Overwrite current file with sample?'
) )
const confirmButton = page.getByRole('button', { name: 'Submit command' }) const confirmButton = page.getByRole('button', { name: 'Submit command' })
const projectMenuButton = page.getByTestId('project-sidebar-toggle') const projectMenuButton = page.getByTestId('project-sidebar-toggle')

View File

@ -210,7 +210,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`part001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ)
|> startProfileAt([5 + 0, 20 + 0], %) |> startProfileAt([5 + 0, 20 + 0], %)
|> line(end = [0.5, -14 + 0]) |> line(end = [0.5, -14 + 0])
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
@ -380,7 +381,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yRel001 = -14 `@settings(defaultLengthUnit = in)
yRel001 = -14
xRel001 = 0.5 xRel001 = 0.5
angle001 = 3 angle001 = 3
len001 = 32 len001 = 32
@ -459,7 +461,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`part001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(end = [0.5, -14 + 0]) |> line(end = [0.5, -14 + 0])
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
@ -590,7 +593,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`part001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(end = [0.5, -14 + 0]) |> line(end = [0.5, -14 + 0])
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
@ -751,7 +755,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`part001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(end = [0.5, -14 + 0]) |> line(end = [0.5, -14 + 0])
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
@ -831,7 +836,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([56.37, 120.33], sketch001) profile001 = startProfileAt([56.37, 120.33], sketch001)
|> line(end = [162.86, 106.48]) |> line(end = [162.86, 106.48])
|> arcTo({ |> arcTo({
@ -957,7 +963,8 @@ profile001 = startProfileAt([56.37, 120.33], sketch001)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`part001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ)
|> circle(center = [1 + 0, 0], radius = 8) |> circle(center = [1 + 0, 0], radius = 8)
` `
) )
@ -1077,7 +1084,8 @@ profile001 = startProfileAt([56.37, 120.33], sketch001)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`part001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ)
|>startProfileAt([0, 0], %) |>startProfileAt([0, 0], %)
|> line(end = [0.5, -14 + 0]) |> line(end = [0.5, -14 + 0])
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
@ -1351,7 +1359,8 @@ profile001 = startProfileAt([56.37, 120.33], sketch001)
async ({ lineToBeDeleted, extraLine }) => { async ({ lineToBeDeleted, extraLine }) => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`part001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ)
|> startProfileAt([5, 6], %) |> startProfileAt([5, 6], %)
|> ${lineToBeDeleted} |> ${lineToBeDeleted}
|> line(end = [-10, -15]) |> line(end = [-10, -15])
@ -1516,7 +1525,8 @@ profile001 = startProfileAt([56.37, 120.33], sketch001)
async ({ lineToBeDeleted }) => { async ({ lineToBeDeleted }) => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`part001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ)
|> startProfileAt([5, 6], %) |> startProfileAt([5, 6], %)
|> ${lineToBeDeleted} |> ${lineToBeDeleted}
|> line(end = [-10, -15]) |> line(end = [-10, -15])

View File

@ -68,20 +68,20 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
await u.closeDebugPanel() await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)` `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
) )
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001) .toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|> xLine(length = ${commonPoints.num1})`) |> xLine(length = ${commonPoints.num1})`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${ .toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt commonPoints.startAt
}, sketch001) }, sketch001)
|> xLine(length = ${commonPoints.num1}) |> xLine(length = ${commonPoints.num1})
@ -89,7 +89,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20) await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${ .toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt commonPoints.startAt
}, sketch001) }, sketch001)
|> xLine(length = ${commonPoints.num1}) |> xLine(length = ${commonPoints.num1})
@ -260,7 +260,8 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([-79.26, 95.04], %) |> startProfileAt([-79.26, 95.04], %)
|> line(end = [112.54, 127.64], tag = $seg02) |> line(end = [112.54, 127.64], tag = $seg02)
|> line(end = [170.36, -121.61], tag = $seg01) |> line(end = [170.36, -121.61], tag = $seg01)
@ -528,7 +529,8 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => { await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`part001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
part001 = startSketchOn(XZ)
|> startProfileAt([20, 0], %) |> startProfileAt([20, 0], %)
|> line(end = [7.13, 4 + 0]) |> line(end = [7.13, 4 + 0])
|> angledLine({ angle = 3 + 0, length = 3.14 + 0 }, %) |> angledLine({ angle = 3 + 0, length = 3.14 + 0 }, %)
@ -747,7 +749,8 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
await page.waitForTimeout(200) await page.waitForTimeout(200)
await u.removeCurrentCode() await u.removeCurrentCode()
await u.codeLocator.fill(`sketch001 = startSketchOn(XZ) await u.codeLocator.fill(`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag] |> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|> angledLine([0, 268.43], %, $rectangleSegmentA001) |> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([ |> angledLine([
@ -965,7 +968,8 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
async ({ cases }) => { async ({ cases }) => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`yo = 79 `@settings(defaultLengthUnit = in)
yo = 79
part001 = startSketchOn(XZ) part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %) |> startProfileAt([-7.54, -26.74], %)
|> ${cases[0].expectedCode} |> ${cases[0].expectedCode}
@ -1020,7 +1024,8 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([-79.26, 95.04], %) |> startProfileAt([-79.26, 95.04], %)
|> line(end = [112.54, 127.64]) |> line(end = [112.54, 127.64])
|> line(end = [170.36, -121.61], tag = $seg01) |> line(end = [170.36, -121.61], tag = $seg01)
@ -1253,7 +1258,7 @@ profile001 = startProfileAt([7.49, 9.96], sketch001)
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn(XZ)` `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)`
) )
await page.waitForTimeout(600) await page.waitForTimeout(600)

View File

@ -271,7 +271,7 @@ test.describe('Testing settings', () => {
const bracketDir = join(dir, projectName) const bracketDir = join(dir, projectName)
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('cylinder-inches.kcl'),
join(bracketDir, 'main.kcl') join(bracketDir, 'main.kcl')
) )
} }
@ -699,19 +699,19 @@ test.describe('Testing settings', () => {
name: 'Current units are: ', name: 'Current units are: ',
}) })
await gizmo.click() await gizmo.click()
const button = page.getByRole('button', { const button = page.locator('ul').getByRole('button', {
name: copy, name: copy,
exact: true, exact: true,
}) })
await button.click() await button.click()
const toastMessage = page.getByText( const toastMessage = page.getByText(
`Set default unit to "${unitOfMeasure}" for this project` `Updated per-file units to ${unitOfMeasure}`
) )
await expect(toastMessage).toBeVisible() await expect(toastMessage).toBeVisible()
} }
await changeUnitOfMeasureInGizmo('in', 'Inches')
await changeUnitOfMeasureInGizmo('ft', 'Feet') await changeUnitOfMeasureInGizmo('ft', 'Feet')
await changeUnitOfMeasureInGizmo('in', 'Inches')
await changeUnitOfMeasureInGizmo('yd', 'Yards') await changeUnitOfMeasureInGizmo('yd', 'Yards')
await changeUnitOfMeasureInGizmo('mm', 'Millimeters') await changeUnitOfMeasureInGizmo('mm', 'Millimeters')
await changeUnitOfMeasureInGizmo('cm', 'Centimeters') await changeUnitOfMeasureInGizmo('cm', 'Centimeters')
@ -951,9 +951,9 @@ test.describe('Testing settings', () => {
) )
}) })
await test.step(`Initial units from settings`, async () => { await test.step(`Initial units from settings are ignored`, async () => {
await homePage.openProject('project-000') await homePage.openProject('project-000')
await expect(unitsIndicator).toHaveText('Current units are: in') await expect(unitsIndicator).toHaveText('Current units are: mm')
}) })
await test.step(`Manually write inline settings`, async () => { await test.step(`Manually write inline settings`, async () => {

View File

@ -67,11 +67,11 @@ part001 = startSketchOn(-XZ)
|> xLine(endAbsolute = totalLen, tag = $seg03) |> xLine(endAbsolute = totalLen, tag = $seg03)
|> yLine(length = -armThick, tag = $seg01) |> yLine(length = -armThick, tag = $seg01)
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle = HALF_TURN, angle = turns::HALF_TURN,
offset = -armThick, offset = -armThick,
intersectTag = seg04 intersectTag = seg04
}, %) }, %)
|> angledLineToY([segAng(seg04) + 180, ZERO], %) |> angledLineToY([segAng(seg04) + 180, turns::ZERO], %)
|> angledLineToY({ |> angledLineToY({
angle = -bottomAng, angle = -bottomAng,
to = -totalHeightHalf - armThick, to = -totalHeightHalf - armThick,
@ -79,12 +79,12 @@ part001 = startSketchOn(-XZ)
|> xLine(endAbsolute = segEndX(seg03) + 0) |> xLine(endAbsolute = segEndX(seg03) + 0)
|> yLine(length = -segLen(seg01)) |> yLine(length = -segLen(seg01))
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle = HALF_TURN, angle = turns::HALF_TURN,
offset = -armThick, offset = -armThick,
intersectTag = seg02 intersectTag = seg02
}, %) }, %)
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %) |> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
|> xLine(endAbsolute = ZERO) |> xLine(endAbsolute = turns::ZERO)
|> close() |> close()
|> extrude(length = 4)` |> extrude(length = 4)`
) )
@ -483,7 +483,8 @@ test('Sketch on face', async ({ page, homePage, scene, cmdBar, toolbar }) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn(XZ) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([3.29, 7.86], %) |> startProfileAt([3.29, 7.86], %)
|> line(end = [2.48, 2.44]) |> line(end = [2.48, 2.44])
|> line(end = [2.66, 1.17]) |> line(end = [2.66, 1.17])

23
interface.d.ts vendored
View File

@ -3,6 +3,18 @@ import fsSync from 'node:fs'
import path from 'path' import path from 'path'
import { dialog, shell } from 'electron' import { dialog, shell } from 'electron'
import { MachinesListing } from 'components/MachineManagerProvider' import { MachinesListing } from 'components/MachineManagerProvider'
import type { Channel } from 'src/menu/channels'
import { Menu, WebContents } from 'electron'
import { ZooLabel, ZooMenuEvents } from 'menu/roles'
import type { MenuActionIPC } from 'menu/rules'
import type { WebContentSendPayload } from 'menu/channels'
// Extend the interface with additional custom properties
declare module 'electron' {
interface Menu {
label?: ZooLabel
}
}
type EnvFn = (value?: string) => string type EnvFn = (value?: string) => string
@ -44,6 +56,9 @@ export interface IElectronAPI {
rm: typeof fs.rm rm: typeof fs.rm
stat: (path: string) => ReturnType<fs.stat> stat: (path: string) => ReturnType<fs.stat>
statIsDirectory: (path: string) => Promise<boolean> statIsDirectory: (path: string) => Promise<boolean>
canReadWriteDirectory: (
path: string
) => Promise<{ value: boolean; error: unknown }>
path: typeof path path: typeof path
mkdir: typeof fs.mkdir mkdir: typeof fs.mkdir
join: typeof path.join join: typeof path.join
@ -91,6 +106,14 @@ export interface IElectronAPI {
appCheckForUpdates: () => Promise<unknown> appCheckForUpdates: () => Promise<unknown>
getArgvParsed: () => any getArgvParsed: () => any
getAppTestProperty: (propertyName: string) => any getAppTestProperty: (propertyName: string) => any
// Helper functions to create application Menus
createHomePageMenu: () => Promise<any>
createModelingPageMenu: () => Promise<any>
createFallbackMenu: () => Promise<any>
enableMenu(menuId: string): Promise<any>
disableMenu(menuId: string): Promise<any>
menuOn: (callback: (payload: WebContentSendPayload) => void) => any
} }
declare global { declare global {

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