Compare commits

..

3 Commits

Author SHA1 Message Date
87dfda28a9 Move axes to std constants; move helix, revolve, and mirror2d to be declated in KCL
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-02 17:05:28 +13:00
42f44e11f5 Add Edge type to std
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-03-28 11:18:10 +08:00
16ad7ff77a Move turns to a submodule of std
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-03-28 11:10:51 +08:00
882 changed files with 36773 additions and 74311 deletions

79
.eslintrc Normal file
View File

@ -0,0 +1,79 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": [
"react-perf",
"css-modules",
"jest",
"jsx-a11y",
"react",
"react-hooks",
"suggest-no-throw",
"testing-library",
"@typescript-eslint"
],
"extends": [
"plugin:css-modules/recommended",
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended"
],
"rules": {
"@typescript-eslint/no-empty-object-type": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-unused-vars": ["error", {
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true,
"vars": "all",
"args": "none"
}],
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"no-restricted-globals": [
"error",
{
"name": "isNaN",
"message": "Use Number.isNaN() instead."
},
],
"no-restricted-syntax": [
"error",
{
"selector": "CallExpression[callee.object.name='Array'][callee.property.name='isArray']",
"message": "Use isArray() in lib/utils.ts instead of Array.isArray()."
}
],
"semi": [
"error",
"never"
],
"react-hooks/exhaustive-deps": "off",
"suggest-no-throw/suggest-no-throw": "warn",
},
"overrides": [
{
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
"extends": [
"plugin:testing-library/react"
],
"rules": {
"suggest-no-throw/suggest-no-throw": "off",
"testing-library/prefer-screen-queries": "off",
"jest/valid-expect": "off"
}
},
{
"files": ["src/**/*.test.ts"],
"extends": [
"plugin:testing-library/react"
],
"rules": {
"suggest-no-throw/suggest-no-throw": "off",
}
}
]
}

View File

@ -1,127 +0,0 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": [
"react-perf",
"css-modules",
"jest",
"jsx-a11y",
"react",
"react-hooks",
"suggest-no-throw",
"testing-library",
"@typescript-eslint"
],
"extends": [
"plugin:css-modules/recommended",
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended"
],
"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-extra-non-null-assertion": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-for-in-array": "error",
"no-implied-eval": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-implied-eval": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
"@typescript-eslint/no-redundant-type-constituents": "error",
"@typescript-eslint/no-this-alias": "warn",
"@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",
{
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true,
"vars": "all",
"args": "none"
}
],
"@typescript-eslint/no-unsafe-unary-minus": "error",
"@typescript-eslint/no-wrapper-object-types": "error",
"no-throw-literal": "off", // Use @typescript-eslint/only-throw-error instead.
"@typescript-eslint/only-throw-error": "error",
"@typescript-eslint/prefer-as-const": "warn",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"no-restricted-globals": [
"error",
{
"name": "isNaN",
"message": "Use Number.isNaN() instead."
}
],
"no-restricted-syntax": [
"error",
{
"selector": "CallExpression[callee.object.name='Array'][callee.property.name='isArray']",
"message": "Use isArray() in lib/utils.ts instead of Array.isArray()."
},
{
"selector": "CallExpression[callee.object.name='TOML'][callee.property.name='stringify']",
"message": "Do not use TOML.stringify directly. Use the wrappers in test-utils instead like settingsToToml."
},
{
"selector": "CallExpression[callee.object.name='TOML'][callee.property.name='parse']",
"message": "Do not use TOML.parse directly. Use the wrappers in test-utils instead like tomlToSettings."
}
],
"no-restricted-imports": [
"error",
{
"patterns": [
// Restrict all relative imports except for .css files.
{
"group": ["./*", "../*", "!./*.css", "!../*.css"],
"message": "Use absolute imports instead."
}
]
}
],
"semi": ["error", "never"],
"react-hooks/exhaustive-deps": "off",
"suggest-no-throw/suggest-no-throw": "error"
},
"overrides": [
{
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
"extends": ["plugin:testing-library/react"],
"rules": {
"suggest-no-throw/suggest-no-throw": "off",
"testing-library/prefer-screen-queries": "off",
"jest/valid-expect": "off"
}
},
{
"files": ["src/**/*.test.ts"],
"extends": ["plugin:testing-library/react"],
"rules": {
"suggest-no-throw/suggest-no-throw": "off"
}
},
{
"files": ["packages/**/*.ts", "rust/**/*.ts"],
"extends": [],
"rules": {
"no-restricted-imports": "off"
}
}
]
}

View File

@ -229,6 +229,10 @@ jobs:
timeout_minutes: 30
max_attempts: 3
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 }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
@ -373,7 +377,11 @@ jobs:
timeout_minutes: 45
max_attempts: 15
env:
CI: 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 }}
- uses: actions/upload-artifact@v4

View File

@ -28,87 +28,43 @@ jobs:
- run: yarn fmt-check
yarn-build-wasm:
# Build the wasm blob once on the fastest runner.
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- 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
- run: yarn install
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
cache: false # Configured below.
tool: wasm-pack
- run: yarn build:wasm
yarn-tsc:
runs-on: ubuntu-22.04
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:
workspaces: './rust'
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
tool: wasm-pack
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: Build Wasm
shell: bash
run: yarn build:wasm
- uses: actions/upload-artifact@v4
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:
- 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 build:wasm
- run: yarn tsc
yarn-lint:
runs-on: ubuntu-latest
needs: yarn-build-wasm
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
@ -117,55 +73,9 @@ jobs:
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 --cwd ./rust/kcl-language-server --modules-folder node_modules install
- run: yarn lint
yarn-circular-dependencies:
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 circular-deps:diff
python-codespell:
runs-on: ubuntu-22.04
steps:
@ -181,7 +91,6 @@ jobs:
yarn-unit-test-kcl-samples:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
@ -194,22 +103,7 @@ jobs:
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
tool: wasm-pack
- 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 build:wasm
- run: yarn simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
@ -226,7 +120,6 @@ jobs:
yarn-unit-test:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
@ -239,22 +132,7 @@ jobs:
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
tool: wasm-pack
- 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 build:wasm
- run: yarn simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}

View File

@ -4,38 +4,18 @@ all: install build check
###############################################################################
# INSTALL
ifeq ($(OS),Windows_NT)
CARGO ?= ~/.cargo/bin/cargo.exe
WASM_PACK ?= ~/.cargo/bin/wasm-pack.exe
else
CARGO ?= ~/.cargo/bin/cargo
WASM_PACK ?= ~/.cargo/bin/wasm-pack
endif
WASM_PACK ?= ~/.cargo/bin/wasm-pack
.PHONY: install
install: node_modules/.yarn-integrity $(CARGO) $(WASM_PACK) ## Install dependencies
install: node_modules/.yarn-integrity $(WASM_PACK) ## Install dependencies
node_modules/.yarn-integrity: package.json yarn.lock
yarn install
ifeq ($(OS),Windows_NT)
@ type nul > $@
else
@ touch $@
endif
$(CARGO):
ifeq ($(OS),Windows_NT)
yarn install:rust:windows
else
yarn install:rust
endif
$(WASM_PACK):
ifeq ($(OS),Windows_NT)
yarn install:wasm-pack:cargo
else
yarn install:rust
yarn install:wasm-pack:sh
endif
###############################################################################
# BUILD
@ -51,17 +31,13 @@ VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
build: build-web build-desktop
.PHONY: build-web
build-web: install public/kcl_wasm_lib_bg.wasm build/index.html
build-web: public/kcl_wasm_lib_bg.wasm build/index.html
.PHONY: build-desktop
build-desktop: install 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: $(CARGO_SOURCES) $(RUST_SOURCES)
ifeq ($(OS),Windows_NT)
yarn build:wasm:dev:windows
else
yarn build:wasm:dev
endif
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES)$(RUST_SOURCES)
yarn build:wasm
build/index.html: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
yarn build:local
@ -87,10 +63,8 @@ lint: install ## Lint the code
###############################################################################
# RUN
TARGET ?= web
.PHONY: run
run: run-$(TARGET)
run: run-web
.PHONY: run-web
run-web: install build-web ## Start the web app
@ -116,7 +90,7 @@ test-unit: install ## Run the unit tests
yarn test:unit
.PHONY: test-e2e
test-e2e: test-e2e-$(TARGET)
test-e2e: test-e2e-desktop
.PHONY: test-e2e-web
test-e2e-web: install build-web ## Run the web e2e tests
@ -125,30 +99,22 @@ test-e2e-web: install build-web ## Run the web e2e tests
.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)"
yarn test:playwright:electron --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep=$(E2E_GREP)
###############################################################################
# CLEAN
.PHONY: clean
clean: ## Delete all artifacts
ifeq ($(OS),Windows_NT)
git clean --force -d -X
else
rm -rf .vite/ build/
rm -rf trace.zip playwright-report/ test-results/
rm -rf public/kcl_wasm_lib_bg.wasm
rm -rf rust/*/bindings/ rust/*/pkg/ rust/target/
rm -rf node_modules/ rust/*/node_modules/
endif
.PHONY: help
help: install
ifeq ($(OS),Windows_NT)
@ powershell -Command "Get-Content $(MAKEFILE_LIST) | Select-String -Pattern '^[^\s]+:.*##\s.*$$' | ForEach-Object { $$line = $$_.Line -split ':.*?##\s+'; Write-Host -NoNewline $$line[0].PadRight(30) -ForegroundColor Cyan; Write-Host $$line[1] }"
else
@ grep -E '^[^[:space:]]+:.*## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
endif
.DEFAULT_GOAL := help

View File

@ -54,7 +54,7 @@ example = extrude(exampleSketch, length = 5)
// Add color to a revolved solid.
sketch001 = startSketchOn(XY)
|> circle(center = [15, 0], radius = 5)
|> revolve(angle = 360, axis = 'y')
|> revolve(angle = 360, axis = Y)
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)
```

View File

@ -9,9 +9,12 @@ layout: manual
### `std`
- [`X`](/docs/kcl/consts/std-X)
- [`XY`](/docs/kcl/consts/std-XY)
- [`XZ`](/docs/kcl/consts/std-XZ)
- [`Y`](/docs/kcl/consts/std-Y)
- [`YZ`](/docs/kcl/consts/std-YZ)
- [`Z`](/docs/kcl/consts/std-Z)
### `std::math`

15
docs/kcl/consts/std-X.md Normal file
View File

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

15
docs/kcl/consts/std-Y.md Normal file
View File

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

15
docs/kcl/consts/std-Z.md Normal file
View File

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

File diff suppressed because one or more lines are too long

View File

@ -22,6 +22,9 @@ layout: manual
* [`string`](kcl/types/string)
* [`tag`](kcl/types/tag)
* **std**
* [`Axis2d`](kcl/types/Axis2d)
* [`Axis3d`](kcl/types/Axis3d)
* [`Edge`](kcl/types/Edge)
* [`Face`](kcl/types/Face)
* [`Helix`](kcl/types/Helix)
* [`Plane`](kcl/types/Plane)
@ -29,9 +32,12 @@ layout: manual
* [`Point3d`](kcl/types/Point3d)
* [`Sketch`](kcl/types/Sketch)
* [`Solid`](kcl/types/Solid)
* [`X`](kcl/consts/std-X)
* [`XY`](kcl/consts/std-XY)
* [`XZ`](kcl/consts/std-XZ)
* [`Y`](kcl/consts/std-Y)
* [`YZ`](kcl/consts/std-YZ)
* [`Z`](kcl/consts/std-Z)
* [`abs`](kcl/abs)
* [`acos`](kcl/acos)
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
@ -68,7 +74,7 @@ layout: manual
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
* [`getOppositeEdge`](kcl/getOppositeEdge)
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
* [`helix`](kcl/helix)
* [`helix`](kcl/std-helix)
* [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`inch`](kcl/inch)
@ -87,7 +93,6 @@ layout: manual
* [`map`](kcl/map)
* [`max`](kcl/max)
* [`min`](kcl/min)
* [`mirror2d`](kcl/mirror2d)
* [`mm`](kcl/mm)
* [`offsetPlane`](kcl/offsetPlane)
* [`patternCircular2d`](kcl/patternCircular2d)
@ -106,7 +111,7 @@ layout: manual
* [`push`](kcl/push)
* [`reduce`](kcl/reduce)
* [`rem`](kcl/rem)
* [`revolve`](kcl/revolve)
* [`revolve`](kcl/std-revolve)
* [`rotate`](kcl/rotate)
* [`round`](kcl/round)
* [`scale`](kcl/scale)
@ -142,6 +147,7 @@ layout: manual
* [`tan`](kcl/std-math-tan)
* **std::sketch**
* [`circle`](kcl/std-sketch-circle)
* [`mirror2d`](kcl/std-sketch-mirror2d)
* **std::turns**
* [`turns::HALF_TURN`](kcl/consts/std-turns-HALF_TURN)
* [`turns::QUARTER_TURN`](kcl/consts/std-turns-QUARTER_TURN)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -146,7 +146,7 @@ exampleSketch = startSketchOn(XY)
|> line(end = [-2, 0])
|> close()
example = revolve(exampleSketch, axis = 'y', angle = 180)
example = revolve(exampleSketch, axis = Y, angle = 180)
exampleSketch002 = startSketchOn(example, 'end')
|> startProfileAt([4.5, -5], %)
@ -177,7 +177,7 @@ exampleSketch = startSketchOn(XY)
example = revolve(
exampleSketch,
axis = 'y',
axis = Y,
angle = 180,
tagEnd = $end01,
)

116
docs/kcl/std-helix.md Normal file

File diff suppressed because one or more lines are too long

246
docs/kcl/std-revolve.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ circle(@sketch_or_surface: Sketch | Plane | Face, center: Point2d, radius: numbe
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketch_or_surface` | [`Sketch`](/docs/kcl/types/Sketch) OR [`Plane`](/docs/kcl/types/Plane) OR [`Face`](/docs/kcl/types/Face) | Sketch to extend, or plane or surface to sketch on. | Yes |
| `sketch_or_surface` | [`Sketch`](/docs/kcl/types/Sketch) `|` [`Plane`](/docs/kcl/types/Face) `|` [`Plane`](/docs/kcl/types/Face) | Sketch to extend, or plane or surface to sketch on. | Yes |
| `center` | [`Point2d`](/docs/kcl/types/Point2d) | The center of the circle. | Yes |
| `radius` | [`number`](/docs/kcl/types/number) | The radius of the circle. | Yes |
| [`tag`](/docs/kcl/types/tag) | [`tag`](/docs/kcl/types/tag) | Create a new tag which refers to this circle. | No |

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -74,7 +74,7 @@ helixPath = helix(
revolutions = 4,
length = 10,
radius = 5,
axis = 'Z',
axis = Z,
)
// Create a spring by sweeping around the helix path.

File diff suppressed because one or more lines are too long

12
docs/kcl/types/Axis2d.md Normal file
View File

@ -0,0 +1,12 @@
---
title: "std::Axis2d"
excerpt: "An infinte line in 2d space."
layout: manual
---
An infinte line in 2d space.

12
docs/kcl/types/Axis3d.md Normal file
View File

@ -0,0 +1,12 @@
---
title: "std::Axis3d"
excerpt: "An infinte line in 3d space."
layout: manual
---
An infinte line in 3d space.

12
docs/kcl/types/Edge.md Normal file
View File

@ -0,0 +1,12 @@
---
title: "std::Edge"
excerpt: "The edge of a solid."
layout: manual
---
The edge of a solid.

View File

@ -28,7 +28,7 @@ An extrude plane.
| `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 |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| 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 |
| [`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 |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
----
@ -68,7 +68,7 @@ Geometry metadata.
| `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 |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
----
@ -88,7 +88,7 @@ Geometry metadata.
| `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 |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
----

View File

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

View File

@ -17,7 +17,7 @@ A helix.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `value` |[`string`](/docs/kcl/types/string)| The id of the helix. | No |
| `artifactId` |[`string`](/docs/kcl/types/string)| The artifact ID. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `revolutions` |[`number`](/docs/kcl/types/number)| Number of revolutions. | No |
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | 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 |
|----------|------|-------------|----------|
| `type` |enum: `Module`| | No |
| `value` |`integer`| Identifier of a source file. Uses a u32 to keep the size small. | No |
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No |
----

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
test.describe('Electron app header tests', () => {
test(

View File

@ -1,14 +1,13 @@
import type { Page } from '@playwright/test'
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import {
PERSIST_MODELING_CONTEXT,
getUtils,
TEST_COLORS,
commonPoints,
getUtils,
PERSIST_MODELING_CONTEXT,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import { HomePageFixture } from './fixtures/homePageFixture'
test.setTimeout(120000)
@ -86,7 +85,7 @@ async function doBasicSketch(
await page.mouse.click(startXPx, 500 - PUR * 20)
if (openPanes.includes('code')) {
await expect(u.codeLocator)
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1})
@ -120,7 +119,10 @@ async function doBasicSketch(
await page.waitForTimeout(100)
if (openPanes.includes('code')) {
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
await expect(
await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)
).toBeLessThan(3)
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
}
// hold down shift
@ -143,7 +145,7 @@ async function doBasicSketch(
// Open the code pane.
await u.openKclCodePanel()
await expect(u.codeLocator)
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1}, tag = $seg01)

View File

@ -1,118 +0,0 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { expect, test } from '@e2e/playwright/zoo-test'
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

@ -1,11 +1,10 @@
import type { Page } from '@playwright/test'
import type { EngineCommand } from '@src/lang/std/artifactGraph'
import { uuidv4 } from '@src/lib/utils'
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { HomePageFixture } from './fixtures/homePageFixture'
import { getUtils } from './test-utils'
import { EngineCommand } from 'lang/std/artifactGraph'
import { uuidv4 } from 'lib/utils'
import { SceneFixture } from './fixtures/sceneFixture'
test.describe(
'Can create sketches on all planes and their back sides',
@ -47,7 +46,7 @@ test.describe(
},
}
const code = `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)`
const code = `sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)`
await u.openDebugPanel()

View File

@ -1,14 +1,13 @@
import { bracket } from '@src/lib/exampleKcl'
import fsp from 'fs/promises'
import { join } from 'path'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from '@e2e/playwright/storageStates'
import { test, expect } from './zoo-test'
import {
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
getUtils,
executorInputPath,
} from './test-utils'
import { join } from 'path'
import { bracket } from 'lib/exampleKcl'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
import fsp from 'fs/promises'
test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
test('Typing KCL errors induces a badge on the code pane button', async ({
@ -252,11 +251,11 @@ test(
])
await Promise.all([
fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('router-template-slate.kcl'),
join(routerTemplateDir, 'main.kcl')
),
fsp.copyFile(
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
),
])

View File

@ -1,13 +1,12 @@
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
import { test, expect } from './zoo-test'
import * as fsp from 'fs/promises'
import path, { join } from 'path'
import {
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
import path, { join } from 'path'
test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
test('Extrude from command bar selects extrude line after', async ({

View File

@ -1,5 +1,5 @@
import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
import { getUtils } from './test-utils'
test.describe('Copilot ghost text', () => {
// eslint-disable-next-line jest/valid-title

View File

@ -1,5 +1,6 @@
import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
import { getUtils } from './test-utils'
function countNewlines(input: string): number {
let count = 0

View File

@ -1,12 +1,11 @@
import fsp from 'fs/promises'
import { test, expect } from './zoo-test'
import path from 'path'
import {
getUtils,
executorInputPath,
getPlaywrightDownloadDir,
getUtils,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import fsp from 'fs/promises'
test(
'export works on the first try',
@ -21,11 +20,11 @@ test(
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
await Promise.all([
fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('router-template-slate.kcl'),
path.join(bracketDir, 'other.kcl')
),
fsp.copyFile(
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
path.join(bracketDir, 'main.kcl')
),
])
@ -108,7 +107,7 @@ test(
},
{ timeout: 15_000 }
)
.toBeGreaterThan(30_000)
.toBeGreaterThan(300_000)
})
})
@ -188,7 +187,7 @@ test(
},
{ timeout: 15_000 }
)
.toBeGreaterThan(50_000)
.toBeGreaterThan(70_000)
})
})
}

View File

@ -1,14 +1,14 @@
import { uuidv4 } from '@src/lib/utils'
import { test, expect } from './zoo-test'
import fsp from 'fs/promises'
import { join } from 'path'
import { uuidv4 } from 'lib/utils'
import {
TEST_COLORS,
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
TEST_COLORS,
} from './test-utils'
import { join } from 'path'
test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
test('can comment out code with ctrl+/', async ({ page, homePage }) => {
@ -32,30 +32,26 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('/')
await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
|> line(end = [-20, 0])
// |> close()`.replaceAll('\n', '')
)
// |> close()`)
// uncomment the code
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('/')
await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
|> line(end = [-20, 0])
|> close()`.replaceAll('\n', '')
)
|> close()`)
})
test('ensure we use the cache, and do not re-execute', async ({
@ -182,15 +178,13 @@ sketch001 = startSketchOn(XY)
await page.locator('#code-pane button:first-child').click()
await page.locator('button:has-text("Format code")').click()
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
|> line(end = [-20, 0])
|> close()`.replaceAll('\n', '')
)
|> close()`)
})
test('if you click the format button it formats your code and executes so lints are still there', async ({
@ -233,15 +227,13 @@ sketch001 = startSketchOn(XY)
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch_001 = startSketchOn(XY)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch_001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
|> line(end = [-20, 0])
|> close()`.replaceAll('\n', '')
)
|> close()`)
// error in guter
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
@ -479,7 +471,6 @@ sketch_001 = startSketchOn(XY)
test('if you write kcl with lint errors you get lints', async ({
page,
homePage,
scene,
}) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1000, height: 500 })
@ -499,7 +490,10 @@ sketch_001 = startSketchOn(XY)
await page.keyboard.press('ArrowLeft')
await page.keyboard.press('ArrowRight')
await scene.waitForExecutionDone()
// FIXME: lsp errors do not propagate to the frontend until engine is connected and code is executed
// 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
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
@ -821,12 +815,10 @@ sketch_001 = startSketchOn(XY)
// there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
|> xLine(%, length = 5) // lin`)
// expect there to be no KCL errors
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
@ -896,12 +888,10 @@ sketch001 = startSketchOn(XZ)
// there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
|> xLine(%, length = 5) // lin`)
})
})
test('Can undo a click and point extrude with ctrl+z', async ({
@ -985,13 +975,12 @@ sketch001 = startSketchOn(XZ)
test(
'Can undo a sketch modification with ctrl+z',
{ tag: ['@skipWin'] },
async ({ page, homePage, editor }) => {
async ({ page, homePage }) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit=in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
@ -1081,45 +1070,41 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %)
|> line(end = [15.4, -2.78])
|> tangentialArcTo([27.6, -3.05], %)
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
)
|> extrude(length = 5)
`)
// Hit undo
await page.keyboard.down('Control')
await page.keyboard.press('KeyZ')
await page.keyboard.up('Control')
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %)
|> line(end = [15.4, -2.78])
|> tangentialArcTo([24.95, -0.38], %)
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
)
|> extrude(length = 5)`)
// Hit undo again.
await page.keyboard.down('Control')
await page.keyboard.press('KeyZ')
await page.keyboard.up('Control')
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
)
|> extrude(length = 5)
`)
// Hit undo again.
await page.keyboard.down('Control')
@ -1127,15 +1112,13 @@ sketch001 = startSketchOn(XZ)
await page.keyboard.up('Control')
await page.waitForTimeout(100)
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
|> close()
|> extrude(length = 5)`)
}
)
@ -1223,55 +1206,4 @@ sketch001 = startSketchOn(XZ)
})
}
)
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

@ -1,8 +1,7 @@
import { test, expect } from './zoo-test'
import * as fsp from 'fs/promises'
import { join } from 'path'
import { expect, test } from '@e2e/playwright/zoo-test'
const FEATURE_TREE_EXAMPLE_CODE = `export fn timesFive(x) {
return 5 * x
}
@ -22,7 +21,7 @@ sketch001 = startSketchOn(XZ)
|> angledLine([-45, length001], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
revolve001 = revolve(sketch001, axis = "X")
revolve001 = revolve(sketch001, axis = X)
triangle()
|> extrude(length = 30)
plane001 = offsetPlane(XY, offset = 10)
@ -127,7 +126,7 @@ test.describe('Feature Tree pane', () => {
await testViewSource({
operationName: 'Revolve',
operationIndex: 0,
expectedActiveLine: 'revolve001 = revolve(sketch001, axis = "X")',
expectedActiveLine: 'revolve001 = revolve(sketch001, axis = X)',
})
await testViewSource({
operationName: 'Triangle',

View File

@ -1,16 +1,15 @@
import { FILE_EXT } from '@src/lib/constants'
import * as fs from 'fs'
import { test, expect } from './zoo-test'
import * as fsp from 'fs/promises'
import { join } from 'path'
import * as fs from 'fs'
import {
createProject,
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
runningOnWindows,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import { join } from 'path'
import { FILE_EXT } from 'lib/constants'
test.describe('integrations tests', () => {
test(

View File

@ -1,5 +1,5 @@
import type { Locator, Page, Request, Route, TestInfo } from '@playwright/test'
import { expect } from '@playwright/test'
import type { Page, Locator, Route, Request } from '@playwright/test'
import { expect, TestInfo } from '@playwright/test'
import * as fs from 'fs'
import * as path from 'path'

View File

@ -1,12 +1,11 @@
import type { Locator, Page } from '@playwright/test'
import type { Page, Locator } from '@playwright/test'
import { expect } from '@playwright/test'
import {
checkIfPaneIsOpen,
closePane,
checkIfPaneIsOpen,
openPane,
sansWhitespace,
} from '@e2e/playwright/test-utils'
} from '../test-utils'
interface EditorState {
activeLines: Array<string>
@ -82,13 +81,6 @@ export class EditorFixture {
expectEditor = {
toContain: this._expectEditorToContain(),
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 }) => {
const wasPaneOpen = await this.checkIfPaneIsOpen()

View File

@ -1,28 +1,28 @@
/* eslint-disable react-hooks/rules-of-hooks */
import type {
BrowserContext,
ElectronApplication,
Page,
TestInfo,
Page,
} from '@playwright/test'
import { _electron as electron } from '@playwright/test'
import { SETTINGS_FILE_NAME } from '@src/lib/constants'
import type { DeepPartial } from '@src/lib/types'
import * as TOML from '@iarna/toml'
import { TEST_SETTINGS } from '../storageStates'
import { SETTINGS_FILE_NAME } from 'lib/constants'
import { getUtils, setup } from '../test-utils'
import fsp from 'fs/promises'
import fs from 'node:fs'
import path from 'path'
import type { Settings } from '@rust/kcl-lib/bindings/Settings'
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import { TEST_SETTINGS } from '@e2e/playwright/storageStates'
import { getUtils, settingsToToml, setup } from '@e2e/playwright/test-utils'
import { CmdBarFixture } from './cmdBarFixture'
import { EditorFixture } from './editorFixture'
import { ToolbarFixture } from './toolbarFixture'
import { SceneFixture } from './sceneFixture'
import { HomePageFixture } from './homePageFixture'
import { DeepPartial } from 'lib/types'
import { Settings } from '@rust/kcl-lib/bindings/Settings'
export class AuthenticatedApp {
public readonly page: Page
@ -124,7 +124,6 @@ export class ElectronZoo {
// We need to expose this in order for some tests that require folder
// creation and some code below.
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this
const options = {
@ -287,30 +286,26 @@ export class ElectronZoo {
let settingsOverridesToml = ''
if (appSettings) {
settingsOverridesToml = settingsToToml({
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
project_directory: this.projectDirName,
...appSettings.app,
},
project: {
...TEST_SETTINGS.project,
directory: this.projectDirName,
},
},
})
} else {
settingsOverridesToml = settingsToToml({
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
app: {
...TEST_SETTINGS.app,
},
project: {
...TEST_SETTINGS.project,
directory: this.projectDirName,
project_directory: this.projectDirName,
},
},
})

View File

@ -1,4 +1,4 @@
import type { Locator, Page } from '@playwright/test'
import type { Page, Locator } from '@playwright/test'
import { expect } from '@playwright/test'
interface ProjectCardState {

View File

@ -1,17 +1,15 @@
import type { Locator, Page } from '@playwright/test'
import { isArray, uuidv4 } from '@src/lib/utils'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { Page, Locator } from '@playwright/test'
import { expect } from '../zoo-test'
import { isArray, uuidv4 } from 'lib/utils'
import { CmdBarFixture } from './cmdBarFixture'
import {
closeDebugPanel,
doAndWaitForImageDiff,
getPixelRGBs,
getUtils,
openAndClearDebugPanel,
sendCustomCmd,
} from '@e2e/playwright/test-utils'
import { expect } from '@e2e/playwright/zoo-test'
getUtils,
} from '../test-utils'
type MouseParams = {
pixelDiff?: number
@ -312,9 +310,7 @@ export async function expectPixelColor(
.toBeTruthy()
.catch((cause) => {
throw new Error(
`ExpectPixelColor: point ${JSON.stringify(
coords
)} was expecting ${colour} but got ${finalValue}`,
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
{ cause }
)
})

View File

@ -1,18 +1,14 @@
import { type Locator, type Page, test } from '@playwright/test'
import type { SidebarType } from '@src/components/ModelingSidebar/ModelingPanes'
import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants'
import type { ToolbarModeName } from '@src/lib/toolbar'
import { type Page, type Locator, test } from '@playwright/test'
import { expect } from '../zoo-test'
import {
checkIfPaneIsOpen,
closePane,
doAndWaitForImageDiff,
openPane,
} from '@e2e/playwright/test-utils'
import { expect } from '@e2e/playwright/zoo-test'
import { type baseUnitLabels } from '@src/lib/settings/settingsTypes'
type LengthUnitLabel = (typeof baseUnitLabels)[keyof typeof baseUnitLabels]
} from '../test-utils'
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
import { ToolbarModeName } from 'lib/toolbar'
export class ToolbarFixture {
public page: Page
@ -185,14 +181,6 @@ export class ToolbarFixture {
).toBeVisible()
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 () => {
await this.page
@ -239,12 +227,6 @@ export class ToolbarFixture {
async checkIfFeatureTreePaneIsOpen() {
return this.checkIfPaneIsOpen(this.featureTreeId)
}
async selectUnit(unit: LengthUnitLabel) {
await this.page.getByTestId('units-menu').click()
const optionLocator = this.page.getByRole('button', { name: unit })
await expect(optionLocator).toBeVisible()
await optionLocator.click()
}
/**
* Get a specific operation button from the Feature Tree pane.

View File

@ -258,6 +258,14 @@ export const isErrorWhitelisted = (exception: Error) => {
foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
},
// TODO: fix this error in the code
{
name: 'TypeError',
message: "Cannot read properties of undefined (reading 'length')",
stack: '',
project: 'Google Chrome',
foundInSpec: '', // many tests are impacted by this error
},
// TODO: fix this error in the code
{
name: 'ReferenceError',
message: '_testUtils is not defined',
@ -265,6 +273,7 @@ export const isErrorWhitelisted = (exception: Error) => {
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/snapshot-tests.spec.ts',
},
// TODO: fix this error in the code
{
name: 'TypeError',
message: 'Failed to fetch',
@ -272,13 +281,21 @@ export const isErrorWhitelisted = (exception: Error) => {
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/snapshot-tests.spec.ts',
},
// TODO: fix this error in the code
{
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',
name: 'ReferenceError',
message: 'originalCode is not defined',
stack: '',
project: 'Google Chrome',
foundInSpec: '', // many tests are impacted by this error
foundInSpec: 'e2e/playwright/onboarding-tests.spec.ts',
},
// TODO: fix this error in the code
{
name: 'ReferenceError',
message: 'createNewVariableCheckbox is not defined',
stack: '',
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/testing-constraints.spec.ts',
},
]

View File

@ -1,8 +1,7 @@
import fsp from 'fs/promises'
import { test, expect } from './zoo-test'
import { executorInputPath } from './test-utils'
import { join } from 'path'
import { executorInputPath } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import fsp from 'fs/promises'
test(
'When machine-api server not found butt is disabled and shows the reason',
@ -12,7 +11,7 @@ test(
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
})
@ -52,7 +51,7 @@ test(
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
})

View File

@ -1,15 +1,13 @@
import { PROJECT_SETTINGS_FILE_NAME } from '@src/lib/constants'
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 type { NamedView } from '@rust/kcl-lib/bindings/NamedView'
import {
createProject,
perProjectsettingsToToml,
tomlToPerProjectSettings,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
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

View File

@ -1,4 +1,4 @@
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
/**
* Not all menu actions are tested. Some are default electron menu actions.
@ -10,7 +10,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('File.Create project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const newProject =
@ -30,7 +29,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('File.Open project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const openProject =
@ -54,7 +52,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const userSettings = app.applicationMenu.getMenuItemById(
@ -78,7 +75,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const keybindings = app.applicationMenu.getMenuItemById(
@ -100,7 +96,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -117,7 +112,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('File.Preferences.Theme', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -142,7 +136,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -159,7 +152,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('File.Preferences.Sign out', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById('File.Sign out')
@ -178,7 +170,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('Edit.Rename project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -197,7 +188,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('Edit.Delete project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -220,7 +210,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -239,7 +228,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('View.Command Palette...', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -257,7 +245,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('Help.Show all commands', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -273,7 +260,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('Help.KCL code samples', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -289,7 +275,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
@ -308,7 +293,6 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
test('Help.Reset onboarding', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(

View File

@ -2,7 +2,8 @@
// application, check it can make it to the project pane, and nothing more.
// It also tests our test wrappers are working.
// Additionally this serves as a nice minimal example.
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
test.describe('Open the application', () => {
test('see the project view', async ({ page, context }) => {

View File

@ -1,23 +1,22 @@
import { bracket } from '@src/lib/exampleKcl'
import { onboardingPaths } from '@src/routes/Onboarding/paths'
import fsp from 'fs/promises'
import { test, expect } from './zoo-test'
import { join } from 'path'
import { expectPixelColor } from '@e2e/playwright/fixtures/sceneFixture'
import fsp from 'fs/promises'
import {
getUtils,
executorInputPath,
createProject,
settingsToToml,
orRunWhenFullSuiteEnabled,
} from './test-utils'
import { bracket } from 'lib/exampleKcl'
import { onboardingPaths } from 'routes/Onboarding/paths'
import {
TEST_SETTINGS_KEY,
TEST_SETTINGS_ONBOARDING_EXPORT,
TEST_SETTINGS_ONBOARDING_START,
TEST_SETTINGS_ONBOARDING_EXPORT,
TEST_SETTINGS_ONBOARDING_USER_MENU,
} from '@e2e/playwright/storageStates'
import {
createProject,
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
settingsToToml,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './storageStates'
import { expectPixelColor } from './fixtures/sceneFixture'
// Because our default test settings have the onboardingStatus set to 'dismissed',
// we must set it to empty for the tests where we want to see the onboarding immediately.
@ -231,9 +230,9 @@ test.describe('Onboarding tests', () => {
// Override beforeEach test setup
await context.addInitScript(
async ({ settingsKey, settings, code }) => {
async ({ settingsKey, settings }) => {
// Give some initial code, so we can test that it's cleared
localStorage.setItem('persistCode', code)
localStorage.setItem('persistCode', originalCode)
localStorage.setItem(settingsKey, settings)
},
{
@ -241,7 +240,6 @@ test.describe('Onboarding tests', () => {
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
}),
code: originalCode,
}
)

View File

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test'
import { test, expect } from '@playwright/test'
/** @deprecated, import from ./fixtureSetup.ts instead */
export const _test = test

View File

@ -1,12 +1,12 @@
import type { Locator, Page } from '@playwright/test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { EditorFixture } from './fixtures/editorFixture'
import { SceneFixture } from './fixtures/sceneFixture'
import { ToolbarFixture } from './fixtures/toolbarFixture'
import fs from 'node:fs/promises'
import path from 'node:path'
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import { getUtils, orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { getUtils, orRunWhenFullSuiteEnabled } from './test-utils'
import { Locator } from '@playwright/test'
// test file is for testing point an click code gen functionality that's not sketch mode related
@ -137,7 +137,7 @@ test.describe('Point-and-click tests', () => {
await scene.moveCameraTo(cameraPos, cameraTarget)
await test.step('check chamfer selection changes cursor position', async () => {
await test.step('check chamfer selection changes cursor positon', async () => {
await expect(async () => {
// sometimes initial click doesn't register
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 editor.expectState({
activeLines: ['@settings(defaultLengthUnit = in)'],
activeLines: ['sketch001 = startSketchOn(XZ)'],
highlightedCode: '',
diagnostics: [],
})
@ -299,8 +299,7 @@ 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 editor.expectEditor.toContain(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([
@ -370,7 +369,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
})
})
test('Works on chamfers that are not in a pipeExpression can break up multi edges in a chamfer array', async ({
test('Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array', async ({
context,
page,
homePage,
@ -419,8 +418,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|>close()`,
})
await editor.expectEditor.toContain(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([75.8, 317.2], %)
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([
@ -1099,6 +1097,7 @@ openSketch = startSketchOn(XY)
Mode: '',
AngleStart: '',
Revolutions: '',
Length: '',
Radius: '',
CounterClockWise: '',
},
@ -1195,14 +1194,14 @@ openSketch = startSketchOn(XY)
{
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,)`,
expectedOutput: `helix001 = helix( axis = seg01, radius = 1, length = 100, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = seg01, radius = 1, length = 50, 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,)`,
expectedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 1, length = 100, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 1, length = 50, revolutions = 20, angleStart = 0, ccw = false,)`,
},
]
helixCases.map(
@ -1245,6 +1244,7 @@ openSketch = startSketchOn(XY)
AngleStart: '',
Mode: '',
CounterClockWise: '',
Length: '',
Radius: '',
Revolutions: '',
},
@ -1261,6 +1261,8 @@ openSketch = startSketchOn(XY)
await cmdBar.progressCmdBar()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('100')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
@ -1269,6 +1271,7 @@ openSketch = startSketchOn(XY)
AngleStart: '0',
Revolutions: '20',
Radius: '1',
Length: '100',
CounterClockWise: '',
},
commandName: 'Helix',
@ -1289,8 +1292,8 @@ openSketch = startSketchOn(XY)
0
)
await operationButton.dblclick()
const initialInput = '1'
const newInput = '5'
const initialInput = '100'
const newInput = '50'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
@ -1299,14 +1302,13 @@ openSketch = startSketchOn(XY)
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: initialInput,
Radius: '1',
Length: initialInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
})
await page
.getByRole('button', { name: 'radius', exact: false })
.click()
await page.keyboard.press('Shift+Backspace')
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput
.locator('.cm-content')
@ -1317,7 +1319,8 @@ openSketch = startSketchOn(XY)
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
Radius: '1',
Length: newInput,
CounterClockWise: '',
},
commandName: 'Helix',
@ -1388,6 +1391,7 @@ extrude001 = extrude(profile001, length = 100)
Mode: '',
AngleStart: '',
Revolutions: '',
Length: '',
Radius: '',
CounterClockWise: '',
},
@ -1641,10 +1645,9 @@ loft001 = loft([sketch001, sketch002])
{
targetType: 'circle',
testPoint: { x: 700, y: 250 },
initialCode: `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(YZ)
initialCode: `sketch001 = startSketchOn('YZ')
profile001 = circle(sketch001, center = [0, 0], radius = 500)
sketch002 = startSketchOn(XZ)
sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(length = -500)
|> tangentialArcTo([-2000, 500], %)`,
@ -1652,8 +1655,7 @@ sketch002 = startSketchOn(XZ)
{
targetType: 'rectangle',
testPoint: { x: 710, y: 255 },
initialCode: `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(YZ)
initialCode: `sketch001 = startSketchOn('YZ')
profile001 = startProfileAt([-400, -400], sketch001)
|> angledLine([0, 800], %, $rectangleSegmentA001)
|> angledLine([
@ -1666,7 +1668,7 @@ profile001 = startProfileAt([-400, -400], sketch001)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(XZ)
sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(length = -500)
|> tangentialArcTo([-2000, 500], %)`,
@ -1810,8 +1812,7 @@ sketch002 = startSketchOn(XZ)
toolbar,
cmdBar,
}) => {
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(YZ)
const initialCode = `sketch001 = startSketchOn(YZ)
|> circle(
center = [0, 0],
radius = 500
@ -2480,8 +2481,7 @@ extrude001 = extrude(profile001, length = 5)
cmdBar,
}) => {
// Code samples
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
const initialCode = `sketch001 = startSketchOn(XY)
|> startProfileAt([-12, -6], %)
|> line(end = [0, 12])
|> line(end = [24, 0])
@ -2773,8 +2773,7 @@ extrude001 = extrude(sketch001, length = -12)
toolbar,
}) => {
// Code samples
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
const initialCode = `sketch001 = startSketchOn(XY)
|> startProfileAt([-12, -6], %)
|> line(end = [0, 12])
|> line(end = [24, 0], tag = $seg02)
@ -2928,8 +2927,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
toolbar,
cmdBar,
}) => {
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
const initialCode = `sketch001 = startSketchOn(XZ)
|> circle(center = [0, 0], radius = 30)
extrude001 = extrude(sketch001, length = 30)
`
@ -3064,8 +3062,7 @@ extrude001 = extrude(sketch001, length = 30)
toolbar,
cmdBar,
}) => {
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
const initialCode = `sketch001 = startSketchOn(XY)
|> startProfileAt([-20, 20], %)
|> xLine(length = 40)
|> yLine(length = -60)
@ -3183,8 +3180,7 @@ extrude001 = extrude(sketch001, length = 40)
})
const shellSketchOnFacesCases = [
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> circle(center = [0, 0], radius = 100)
|> extrude(length = 100)
@ -3192,8 +3188,7 @@ sketch002 = startSketchOn(sketch001, 'END')
|> circle(center = [0, 0], radius = 50)
|> extrude(length = 50)
`,
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> circle(center = [0, 0], radius = 100)
extrude001 = extrude(sketch001, length = 100)
@ -3474,41 +3469,8 @@ segAng(rectangleSegmentA002),
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = 'X')`
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = X)`
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 ({
context,
@ -3519,22 +3481,26 @@ segAng(rectangleSegmentA002),
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn(XZ)
|> startProfileAt([-102.57, 101.72], %)
|> angledLine([0, 202.6], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
202.6
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const initialCode = `
sketch001 = startSketchOn(XZ)
|> startProfileAt([-102.57, 101.72], %)
|> angledLine([0, 202.6], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
202.6
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch001, length = 50)
sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
|> circle(center = [-11.34, 10.0], radius = 8.69)
|> circle(
center = [-11.34, 10.0],
radius = 8.69
)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
@ -3552,49 +3518,9 @@ sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
const lineCodeToSelection = `|> angledLine([0, 202.6], %, $rectangleSegmentA001)`
await page.getByText(lineCodeToSelection).click()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = rectangleSegmentA001)`
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')
)
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = getOppositeEdge(rectangleSegmentA001)) `
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
})
test('revolve sketch circle around line segment from startProfileAt sketch', async ({
context,
@ -3605,22 +3531,26 @@ sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
toolbar,
cmdBar,
}) => {
const initialCode = `sketch002 = startSketchOn(XY)
|> startProfileAt([-2.02, 1.79], %)
|> xLine(length = 2.6)
sketch001 = startSketchOn(-XY)
|> startProfileAt([-0.48, 1.25], %)
|> angledLine([0, 2.38], %, $rectangleSegmentA001)
|> angledLine([segAng(rectangleSegmentA001) - 90, 2.4], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch001, length = 5)
sketch003 = startSketchOn(extrude001, 'START')
|> circle(center = [-0.69, 0.56], radius = 0.28)
const initialCode = `
sketch002 = startSketchOn(XY)
|> startProfileAt([-2.02, 1.79], %)
|> xLine(length = 2.6)
sketch001 = startSketchOn('-XY')
|> startProfileAt([-0.48, 1.25], %)
|> angledLine([0, 2.38], %, $rectangleSegmentA001)
|> angledLine([segAng(rectangleSegmentA001) - 90, 2.4], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch001, length = 5)
sketch003 = startSketchOn(extrude001, 'START')
|> circle(
center = [-0.69, 0.56],
radius = 0.28
)
`
await context.addInitScript((initialCode) => {
@ -3639,44 +3569,9 @@ sketch003 = startSketchOn(extrude001, 'START')
const lineCodeToSelection = `|> xLine(length = 2.6)`
await page.getByText(lineCodeToSelection).click()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve(sketch003, angle = 360, axis = seg01)`
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)
)
})
})
@ -3689,8 +3584,7 @@ sketch003 = startSketchOn(extrude001, 'START')
toolbar,
cmdBar,
}) => {
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
const initialCode = `sketch001 = startSketchOn(XZ)
profile001 = circle(
sketch001,
center = [0, 0],

View File

@ -1,20 +1,19 @@
import { DEFAULT_PROJECT_KCL_FILE } from '@src/lib/constants'
import fs from 'fs'
import fsp from 'fs/promises'
import path from 'path'
import type { Paths } from '@e2e/playwright/test-utils'
import { test, expect } from './zoo-test'
import {
createProject,
doExport,
executorInputPath,
getPlaywrightDownloadDir,
getUtils,
isOutOfViewInScrollContainer,
Paths,
createProject,
getPlaywrightDownloadDir,
orRunWhenFullSuiteEnabled,
runningOnWindows,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import fsp from 'fs/promises'
import fs from 'fs'
import path from 'path'
import { DEFAULT_PROJECT_KCL_FILE } from 'lib/constants'
test(
'projects reload if a new one is created, deleted, or renamed externally',
@ -88,7 +87,7 @@ test(
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
@ -125,7 +124,7 @@ test(
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
path.join(bracketDir, 'main.kcl')
)
const errorDir = path.join(dir, 'broken-code')
@ -163,7 +162,7 @@ test(
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [110, 110, 110]), {
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(20)
@ -214,7 +213,7 @@ test(
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
path.join(bracketDir, 'main.kcl')
)
const emptyDir = path.join(dir, 'empty')
@ -249,7 +248,7 @@ test(
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
@ -291,7 +290,7 @@ test(
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
path.join(bracketDir, 'main.kcl')
)
@ -320,7 +319,7 @@ test(
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
@ -360,7 +359,7 @@ test(
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
path.join(bracketDir, 'main.kcl')
)
await fsp.copyFile(
@ -394,7 +393,7 @@ test(
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
@ -444,6 +443,7 @@ test(
await page.getByText('broken-code').click()
// 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({
timeout: 20_000,
})
@ -481,7 +481,7 @@ test.describe('Can export from electron app', () => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
@ -513,7 +513,7 @@ test.describe('Can export from electron app', () => {
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
@ -554,7 +554,7 @@ test.describe('Can export from electron app', () => {
},
{ timeout: 15_000 }
)
.toBeGreaterThan(50_000)
.toBeGreaterThan(300_000)
// clean up exported file
await fsp.rm(filepath)
@ -1507,12 +1507,7 @@ test(
await u.waitForPageLoad()
// 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')
await page.locator('.cm-content').fill(`sketch001 = startSketchOn(XZ)
|> startProfileAt([-87.4, 282.92], %)
|> line(end = [324.07, 27.199], tag = $seg01)
|> line(end = [118.328, -291.754])

View File

@ -1,5 +1,4 @@
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
/* eslint-disable jest/no-conditional-expect */
/**

View File

@ -1,5 +1,5 @@
import { orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
import { orRunWhenFullSuiteEnabled } from './test-utils'
/* eslint-disable jest/no-conditional-expect */

View File

@ -1,18 +1,17 @@
import type { Page } from '@playwright/test'
import { bracket } from '@src/lib/exampleKcl'
import { reportRejection } from '@src/lib/trap'
import * as fsp from 'fs/promises'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import path from 'path'
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from '@e2e/playwright/storageStates'
import type { TestColor } from '@e2e/playwright/test-utils'
import * as fsp from 'fs/promises'
import {
TEST_COLORS,
executorInputPath,
getUtils,
executorInputPath,
TEST_COLORS,
TestColor,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
import { bracket } from 'lib/exampleKcl'
import { reportRejection } from 'lib/trap'
test.describe('Regression tests', { tag: ['@skipWin'] }, () => {
// bugs we found that don't fit neatly into other categories
@ -332,7 +331,7 @@ extrude001 = extrude(sketch001, length = 50)
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = mm)
sketch002 = startSketchOn(XY)
sketch002 = startSketchOn('XY')
profile002 = startProfileAt([72.24, -52.05], sketch002)
|> angledLine([0, 181.26], %, $rectangleSegmentA001)
|> angledLine([
@ -583,7 +582,7 @@ extrude002 = extrude(profile002, length = 150)
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
@ -620,7 +619,6 @@ extrude002 = extrude(profile002, length = 150)
test(`View gizmo stays visible even when zoomed out all the way`, async ({
page,
homePage,
scene,
}) => {
const u = await getUtils(page)
@ -634,7 +632,7 @@ extrude002 = extrude(profile002, length = 150)
await test.step(`Load an empty file`, async () => {
await page.addInitScript(async () => {
localStorage.setItem('persistCode', '@settings(defaultLengthUnit = in)')
localStorage.setItem('persistCode', '')
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
@ -648,31 +646,22 @@ extrude002 = extrude(profile002, length = 150)
timeout: 5000,
message: 'Plane color is visible',
})
.toBeLessThanOrEqual(20)
await expect(scene.startEditSketchBtn).toBeEnabled()
.toBeLessThanOrEqual(15)
let maxZoomOuts = 10
let middlePixelIsBackgroundColor =
(await middlePixelIsColor(bgColor)) < 10
console.time('pressing control')
await page.keyboard.down('Control')
while (!middlePixelIsBackgroundColor && maxZoomOuts > 0) {
await page.waitForTimeout(100)
await page.mouse.move(650, 460)
console.time('moved to start point')
await page.keyboard.down('Control')
await page.mouse.move(600, 460)
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.move(600, 50, { steps: 20 })
await page.mouse.up({ button: 'right' })
console.time('moused up')
await page.keyboard.up('Control')
await page.waitForTimeout(100)
maxZoomOuts--
middlePixelIsBackgroundColor = (await middlePixelIsColor(bgColor)) < 15
middlePixelIsBackgroundColor = (await middlePixelIsColor(bgColor)) < 10
}
await page.keyboard.up('Control')
expect(middlePixelIsBackgroundColor, {
message: 'We should not see the default planes',
@ -689,12 +678,13 @@ extrude002 = extrude(profile002, length = 150)
homePage,
scene,
toolbar,
viewport,
}) => {
await context.folderSetupFn(async (dir) => {
const legoDir = path.join(dir, 'lego')
await fsp.mkdir(legoDir, { recursive: true })
await fsp.copyFile(
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
executorInputPath('lego.kcl'),
path.join(legoDir, 'main.kcl')
)
})
@ -707,8 +697,11 @@ extrude002 = extrude(profile002, length = 150)
await scene.loadingIndicator.waitFor({ state: 'detached' })
})
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
// TODO: use the viewport size to pick the center point, but the `viewport` fixture's values were wrong.
await scene.expectPixelColor([116, 116, 116], { x: 500, y: 250 }, 15)
await scene.expectPixelColor(
[143, 143, 143],
{ x: (viewport?.width ?? 1200) / 2, y: (viewport?.height ?? 500) / 2 },
15
)
})
})
@ -798,74 +791,6 @@ plane002 = offsetPlane(XZ, offset = -2 * x)`
await page.getByTestId('custom-cmd-send-button').click()
}
)
test('scale other than default works with sketch mode', async ({
page,
homePage,
toolbar,
editor,
scene,
}) => {
await test.step('Load the washer code', async () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
innerDiameter = 0.203
outerDiameter = 0.438
thicknessMax = 0.038
thicknessMin = 0.024
washerSketch = startSketchOn(XY)
|> circle(center = [0, 0], radius = outerDiameter / 2)
washer = extrude(washerSketch, length = thicknessMax)`
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
})
const [circleCenterClick] = scene.makeMouseHelpers(650, 300)
const [circleRadiusClick] = scene.makeMouseHelpers(800, 320)
const [washerFaceClick] = scene.makeMouseHelpers(657, 286)
await page.waitForTimeout(100)
await test.step('Start sketching on the washer face', async () => {
await toolbar.startSketchPlaneSelection()
await washerFaceClick()
await page.waitForTimeout(600) // engine animation
await toolbar.expectToolbarMode.toBe('sketching')
})
await test.step('Draw a circle and verify code', async () => {
// select circle tool
await expect
.poll(async () => {
await toolbar.circleBtn.click()
return toolbar.circleBtn.getAttribute('aria-pressed')
})
.toBe('true')
await page.waitForTimeout(100)
await circleCenterClick()
// this number will be different if the scale is not set correctly for inches
await editor.expectEditor.toContain(
'circle(sketch001, center = [0.06, -0.06]'
)
await circleRadiusClick()
await editor.expectEditor.toContain(
'circle(sketch001, center = [0.06, -0.06], radius = 0.18'
)
})
await test.step('Exit sketch mode', async () => {
await toolbar.exitSketch()
await toolbar.expectToolbarMode.toBe('modeling')
await toolbar.selectUnit('Yards')
await editor.expectEditor.toContain('@settings(defaultLengthUnit = yd)')
})
})
})
async function clickExportButton(page: Page) {

View File

@ -1,20 +1,20 @@
import type { Page } from '@playwright/test'
import { roundOff, uuidv4 } from '@src/lib/utils'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import fs from 'node:fs/promises'
import path from 'node:path'
import { HomePageFixture } from './fixtures/homePageFixture'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import {
PERSIST_MODELING_CONTEXT,
TEST_COLORS,
getMovementUtils,
getUtils,
PERSIST_MODELING_CONTEXT,
TEST_COLORS,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
} from './test-utils'
import { uuidv4, roundOff } from 'lib/utils'
import { SceneFixture } from './fixtures/sceneFixture'
import { ToolbarFixture } from './fixtures/toolbarFixture'
import { CmdBarFixture } from './fixtures/cmdBarFixture'
test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
@ -113,8 +113,7 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.61, -4.01], %)
|> xLine(length = 8.73)
|> tangentialArcTo([8.33, -1.31], %)`
@ -160,10 +159,7 @@ sketch001 = startSketchOn(XZ)
await page.mouse.click(700, 200)
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
.toBe(`@settings(defaultLengthUnit = in)
sketch002 = startSketchOn(XZ)
.toBe(`sketch002 = startSketchOn(XZ)
sketch001 = startProfileAt([12.34, -12.34], sketch002)
|> yLine(length = 12.34)
@ -479,8 +475,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit=in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> circle(center = [4.61, -5.01], radius = 8)`
)
})
@ -565,14 +560,12 @@ sketch001 = startSketchOn(XZ)
test('Can edit a sketch that has been extruded in the same pipe', async ({
page,
homePage,
editor,
}) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit=in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %)
@ -657,34 +650,31 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([7.12, -12.68], %)
|> line(end = [12.68, -1.09])
|> tangentialArcTo([24.89, 0.68], %)
|> close()
|> extrude(length = 5)`,
{ shouldNormalise: true }
)
|> extrude(length = 5)
`)
})
test('Can edit a sketch that has been revolved in the same pipe', async ({
page,
homePage,
scene,
editor,
}) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit=in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -14.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -5.38], %)
|> close()
|> revolve(axis = "X")`
|> revolve(axis = X)`
)
})
@ -764,16 +754,14 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([6.44, -12.07], %)
|> line(end = [14.72, 1.97])
|> tangentialArcTo([24.95, -5.38], %)
|> line(end = [1.97, 2.06])
|> close()
|> revolve(axis = "X")`,
{ shouldNormalise: true }
)
|> revolve(axis = X)`)
})
test('Can add multiple sketches', async ({ page, homePage }) => {
const u = await getUtils(page)
@ -801,8 +789,7 @@ sketch001 = startSketchOn(XZ)
200
)
let codeStr =
'@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XY)'
let codeStr = 'sketch001 = startSketchOn(XY)'
await page.mouse.click(center.x, viewportSize.height * 0.55)
await expect(u.codeLocator).toHaveText(codeStr)
@ -881,8 +868,7 @@ sketch001 = startSketchOn(XZ)
await u.openDebugPanel()
const code = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(-XZ)
const code = `sketch001 = startSketchOn(-XZ)
profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
scale * 34.8
)}], sketch001)
@ -912,7 +898,7 @@ profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
await page.mouse.move(700, 200, { steps: 10 })
await page.mouse.click(700, 200, { delay: 200 })
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(-XZ)`
`sketch001 = startSketchOn(-XZ)`
)
let prevContent = await page.locator('.cm-content').innerText()
@ -1223,7 +1209,7 @@ profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
|> xLine(endAbsolute = 0 + .001)
|> yLine(endAbsolute = 0)
|> close()
|> revolve(axis = "Y")
|> revolve(axis = Y)
return lugSketch
}
@ -1440,8 +1426,7 @@ test.describe(`Sketching with offset planes`, () => {
await context.addInitScript(() => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
offsetPlane001 = offsetPlane(XY, offset = 10)`
`offsetPlane001 = offsetPlane(XY, offset = 10)`
)
})
@ -1455,7 +1440,7 @@ offsetPlane001 = offsetPlane(XY, offset = 10)`
await test.step(`Hovering should highlight code`, async () => {
await planeHover()
await editor.expectState({
activeLines: [`@settings(defaultLengthUnit = in)`],
activeLines: [`offsetPlane001=offsetPlane(XY,offset=10)`],
diagnostics: [],
highlightedCode: 'offsetPlane(XY, offset = 10)',
})
@ -1468,7 +1453,7 @@ offsetPlane001 = offsetPlane(XY, offset = 10)`
await expect(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
await editor.expectState({
activeLines: [`@settings(defaultLengthUnit = in)`],
activeLines: [`offsetPlane001=offsetPlane(XY,offset=10)`],
diagnostics: [],
highlightedCode: '',
})
@ -1619,8 +1604,7 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
await context.addInitScript(() => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
profile002 = startProfileAt([40.68, 87.67], sketch001)
|> xLine(length = 239.17)
profile003 = startProfileAt([206.63, -56.73], sketch001)
@ -2188,8 +2172,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([6.24, 4.54], sketch001)
|> line(end = [-0.41, 6.99])
|> line(end = [8.61, 0.74])
@ -2334,8 +2317,7 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([6.24, 4.54], sketch001)
|> line(end = [-0.41, 6.99])
|> line(end = [8.61, 0.74])
@ -2440,8 +2422,7 @@ profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([-63.43, 193.08], sketch001)
|> line(end = [168.52, 149.87])
|> line(end = [190.29, -39.18])
@ -2505,11 +2486,7 @@ extrude001 = extrude(profile003, length = 5)
page,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
myVar = 5`
)
localStorage.setItem('persistCode', `myVar = 5`)
})
await page.setBodyDimensions({ width: 1000, height: 500 })
@ -2556,8 +2533,7 @@ extrude001 = extrude(profile003, length = 5)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([85.19, 338.59], sketch001)
|> line(end = [213.3, -94.52])
|> line(end = [-230.09, -55.34])
@ -2599,8 +2575,7 @@ profile002 = startProfileAt([85.81, 52.55], sketch002)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
thePart = startSketchOn(XZ)
`thePart = startSketchOn(XZ)
|> startProfileAt([7.53, 10.51], %)
|> line(end = [12.54, 1.83])
|> line(end = [6.65, -6.91])
@ -2661,8 +2636,7 @@ extrude001 = extrude(thePart, length = 75)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([6.71, -3.66], sketch001)
|> line(end = [2.65, 9.02], tag = $seg02)
|> line(end = [3.73, -9.36], tag = $seg01)
@ -2835,8 +2809,7 @@ extrude003 = extrude(profile011, length = 2.5)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([34, 42.66], sketch001)
|> line(end = [102.65, 151.99])
|> line(end = [76, -138.66])

View File

@ -1,22 +1,21 @@
import type { Models } from '@kittycad/lib'
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
import { spawn } from 'child_process'
import fsp from 'fs/promises'
import JSZip from 'jszip'
import path from 'path'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { secrets } from '@e2e/playwright/secrets'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
import type { Paths } from '@e2e/playwright/test-utils'
import { test, expect } from './zoo-test'
import { secrets } from './secrets'
import {
Paths,
doExport,
getUtils,
orRunWhenFullSuiteEnabled,
settingsToToml,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
orRunWhenFullSuiteEnabled,
} from './test-utils'
import { Models } from '@kittycad/lib'
import fsp from 'fs/promises'
import { spawn } from 'child_process'
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
import JSZip from 'jszip'
import path from 'path'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
import { SceneFixture } from './fixtures/sceneFixture'
import { CmdBarFixture } from './fixtures/cmdBarFixture'
test.beforeEach(async ({ page, context }) => {
// Make the user avatar image always 404
@ -346,9 +345,7 @@ const extrudeDefaultPlane = async (
app: {
onboarding_status: 'dismissed',
show_debug_panel: true,
appearance: {
theme: 'dark',
},
theme: 'dark',
},
project: {
default_project_name: 'project-$nnn',
@ -455,7 +452,7 @@ test(
await page.waitForTimeout(700) // TODO detect animation ending, or disable animation
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
code += `profile001 = startProfileAt([182.59, -246.32], sketch001)`
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
await expect(page.locator('.cm-content')).toHaveText(code)
await page.waitForTimeout(100)
@ -473,7 +470,7 @@ test(
await page.waitForTimeout(500)
code += `
|> xLine(length = 184.3)`
|> xLine(length = 7.25)`
await expect(page.locator('.cm-content')).toHaveText(code)
await page
@ -631,7 +628,7 @@ test(
mask: [page.getByTestId('model-state-indicator')],
})
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn(XZ)profile001 = circle(sketch001, center = [366.89, -62.01], radius = 1)`
`sketch001 = startSketchOn(XZ)profile001 = circle(sketch001, center = [14.44, -2.44], radius = 1)`
)
}
)
@ -668,7 +665,7 @@ test.describe(
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
code += `profile001 = startProfileAt([182.59, -246.32], sketch001)`
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
await expect(u.codeLocator).toHaveText(code)
await page.waitForTimeout(100)
@ -676,7 +673,7 @@ test.describe(
await page.waitForTimeout(100)
code += `
|> xLine(length = 184.3)`
|> xLine(length = 7.25)`
await expect(u.codeLocator).toHaveText(code)
await page
@ -691,7 +688,7 @@ test.describe(
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
code += `
|> tangentialArcTo([551.2, -62.01], %)`
|> tangentialArcTo([21.7, -2.44], %)`
await expect(u.codeLocator).toHaveText(code)
// click tangential arc tool again to unequip it

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

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

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 68 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",
"source_ranges": [
{
@ -30,4 +30,4 @@
}
],
"kcl_version": "0.2.48"
}
}

View File

@ -1,19 +1,17 @@
import type { SaveSettingsPayload } from '@src/lib/settings/settingsTypes'
import { Themes } from '@src/lib/theme'
import type { DeepPartial } from '@src/lib/types'
import { onboardingPaths } from '@src/routes/Onboarding/paths'
import type { Settings } from '@rust/kcl-lib/bindings/Settings'
import { Settings } from '@rust/kcl-lib/bindings/Settings'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { Themes } from 'lib/theme'
import { DeepPartial } from 'lib/types'
import { onboardingPaths } from 'routes/Onboarding/paths'
export const IS_PLAYWRIGHT_KEY = 'playwright'
export const TEST_SETTINGS_KEY = '/settings.toml'
export const TEST_SETTINGS: DeepPartial<Settings> = {
app: {
appearance: {
theme: Themes.Dark,
},
theme: Themes.Dark,
onboarding_status: 'dismissed',
project_directory: '',
show_debug_panel: true,
},
modeling: {
@ -24,7 +22,6 @@ export const TEST_SETTINGS: DeepPartial<Settings> = {
},
project: {
default_project_name: 'project-$nnn',
directory: '',
},
text_editor: {
text_wrapping: true,
@ -57,7 +54,7 @@ export const TEST_SETTINGS_ONBOARDING_START: DeepPartial<Settings> = {
export const TEST_SETTINGS_DEFAULT_THEME: DeepPartial<Settings> = {
...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, appearance: { theme: Themes.System } },
app: { ...TEST_SETTINGS.app, theme: Themes.System },
}
export const TEST_SETTINGS_CORRUPTED = {

View File

@ -1,12 +1,7 @@
import type { EngineCommand } from '@src/lang/std/artifactGraph'
import { uuidv4 } from '@src/lib/utils'
import {
commonPoints,
getUtils,
orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
import { commonPoints, getUtils, orRunWhenFullSuiteEnabled } from './test-utils'
import { EngineCommand } from 'lang/std/artifactGraph'
import { uuidv4 } from 'lib/utils'
test.describe('Test network and connection issues', () => {
test(

View File

@ -1,9 +1,9 @@
import {
orRunWhenFullSuiteEnabled,
runningOnLinux,
runningOnMac,
runningOnWindows,
} from '@e2e/playwright/test-utils'
orRunWhenFullSuiteEnabled,
} from './test-utils'
describe('platform detection utilities', () => {
const originalPlatform = process.platform

View File

@ -1,29 +1,32 @@
import * as TOML from '@iarna/toml'
import type { Models } from '@kittycad/lib'
import type { BrowserContext, Locator, Page, TestInfo } from '@playwright/test'
import { expect } from '@playwright/test'
import type { EngineCommand } from '@src/lang/std/artifactGraph'
import type { Configuration } from '@src/lang/wasm'
import { COOKIE_NAME } from '@src/lib/constants'
import { reportRejection } from '@src/lib/trap'
import type { DeepPartial } from '@src/lib/types'
import { isArray } from '@src/lib/utils'
import {
expect,
BrowserContext,
TestInfo,
Locator,
Page,
} from '@playwright/test'
import { test } from './zoo-test'
import { EngineCommand } from 'lang/std/artifactGraph'
import fsp from 'fs/promises'
import path from 'path'
import pixelMatch from 'pixelmatch'
import type { Protocol } from 'playwright-core/types/protocol'
import { PNG } from 'pngjs'
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
import { secrets } from '@e2e/playwright/secrets'
import { Protocol } from 'playwright-core/types/protocol'
import type { Models } from '@kittycad/lib'
import { COOKIE_NAME } from 'lib/constants'
import { secrets } from './secrets'
import {
IS_PLAYWRIGHT_KEY,
TEST_SETTINGS,
TEST_SETTINGS_KEY,
} from '@e2e/playwright/storageStates'
import { test } from '@e2e/playwright/zoo-test'
TEST_SETTINGS,
IS_PLAYWRIGHT_KEY,
} from './storageStates'
import * as TOML from '@iarna/toml'
import { isErrorWhitelisted } from './lib/console-error-whitelist'
import { isArray } from 'lib/utils'
import { reportRejection } from 'lib/trap'
import { DeepPartial } from 'lib/types'
import { Configuration } from 'lang/wasm'
import { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
const toNormalizedCode = (text: string) => {
return text.replace(/\s+/g, '')
@ -680,8 +683,8 @@ const _makeTemplate = (
isArray(currentOptions)
? currentOptions[i]
: typeof currentOptions === 'number'
? currentOptions
: ''
? currentOptions
: ''
)
)
})
@ -900,21 +903,15 @@ export async function setup(
settings: {
...TEST_SETTINGS,
app: {
appearance: {
...TEST_SETTINGS.app?.appearance,
theme: 'dark',
},
...TEST_SETTINGS.project,
project_directory: TEST_SETTINGS.app?.project_directory,
onboarding_status: 'dismissed',
},
project: {
...TEST_SETTINGS.project,
directory: TEST_SETTINGS.project?.directory,
theme: 'dark',
},
},
}),
IS_PLAYWRIGHT_KEY,
PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.project?.directory || '',
PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app?.project_directory || '',
PERSIST_MODELING_CONTEXT,
}
)
@ -1115,25 +1112,21 @@ export async function pollEditorLinesSelectedLength(page: Page, lines: number) {
}
export function settingsToToml(settings: DeepPartial<Configuration>) {
// eslint-disable-next-line no-restricted-syntax
return TOML.stringify(settings as any)
}
export function tomlToSettings(toml: string): DeepPartial<Configuration> {
// eslint-disable-next-line no-restricted-syntax
return TOML.parse(toml)
}
export function tomlToPerProjectSettings(
toml: string
): DeepPartial<ProjectConfiguration> {
// eslint-disable-next-line no-restricted-syntax
return TOML.parse(toml)
}
export function perProjectsettingsToToml(
settings: DeepPartial<ProjectConfiguration>
) {
// eslint-disable-next-line no-restricted-syntax
return TOML.stringify(settings as any)
}

View File

@ -1,8 +1,7 @@
import type { EngineCommand } from '@src/lang/std/artifactGraph'
import { uuidv4 } from '@src/lib/utils'
import { getUtils, orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
import { EngineCommand } from 'lang/std/artifactGraph'
import { uuidv4 } from 'lib/utils'
import { getUtils, orRunWhenFullSuiteEnabled } from './test-utils'
test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
test('Can move camera reliably', async ({ page, context, homePage }) => {

View File

@ -1,14 +1,14 @@
import { XOR } from '@src/lib/utils'
import { test, expect } from './zoo-test'
import * as fsp from 'fs/promises'
import path from 'node:path'
import {
TEST_COLORS,
getUtils,
orRunWhenFullSuiteEnabled,
TEST_COLORS,
pollEditorLinesSelectedLength,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
executorInputPath,
orRunWhenFullSuiteEnabled,
} from './test-utils'
import { XOR } from 'lib/utils'
import path from 'node:path'
test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
test('Can constrain line length', async ({ page, homePage }) => {
@ -81,8 +81,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 79
`yo = 79
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4], tag = $seg01)
@ -146,8 +145,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 5
`yo = 5
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4], tag = $seg01)
@ -161,6 +159,31 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|> xLine(length = segLen(seg_what))
|> 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)
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -254,8 +277,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 5
`yo = 5
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4])
@ -365,8 +387,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 5
`yo = 5
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4])
@ -479,8 +500,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 5
`yo = 5
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4])
@ -582,8 +602,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 5
`yo = 5
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4])
@ -669,8 +688,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 5
`yo = 5
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4])
@ -750,8 +768,7 @@ part002 = startSketchOn(XZ)
await page.addInitScript(async (customCode) => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 5
`yo = 5
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4])
@ -852,8 +869,7 @@ part002 = startSketchOn(XZ)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 5
`yo = 5
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4])
@ -934,8 +950,7 @@ part002 = startSketchOn(XZ)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
yo = 5
`yo = 5
part001 = startSketchOn(XZ)
|> startProfileAt([-7.54, -26.74], %)
|> line(end = [74.36, 130.4])
@ -1098,23 +1113,13 @@ test.describe('Electron constraint tests', () => {
test(
'Able to double click label to set constraint',
{ tag: '@electron' },
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
async ({ page, context, homePage, scene, editor, toolbar }) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.writeFile(
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'
await fsp.copyFile(
executorInputPath('angled_line.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
@ -1132,14 +1137,6 @@ test.describe('Electron constraint tests', () => {
await scene.waitForExecutionDone()
})
async function clickOnFirstSegmentLabel() {
const child = page
.locator('.segment-length-label-text')
.first()
.locator('xpath=..')
await child.dblclick()
}
await test.step('Double click to constrain', async () => {
// Enter sketch edit mode via feature tree
await toolbar.openPane('feature-tree')
@ -1147,19 +1144,21 @@ test.describe('Electron constraint tests', () => {
await op.dblclick()
await toolbar.closePane('feature-tree')
await clickOnFirstSegmentLabel()
await cmdBar.progressCmdBar()
await editor.expectEditor.toContain('length001 = 15.3')
await editor.expectEditor.toContain('|> angledLine([9, length001], %)')
})
await test.step('Double click again and expect failure', async () => {
await clickOnFirstSegmentLabel()
await expect(
page.getByText('Unable to constrain the length of this segment')
).toBeVisible()
const child = page
.locator('.segment-length-label-text')
.first()
.locator('xpath=..')
await child.dblclick()
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
await cmdBarSubmitButton.click()
await expect(page.locator('.cm-content')).toContainText(
'length001 = 15.3'
)
await expect(page.locator('.cm-content')).toContainText(
'|> angledLine([9, length001], %)'
)
await page.getByRole('button', { name: 'Exit Sketch' }).click()
})
}

View File

@ -1,8 +1,7 @@
import { uuidv4 } from '@src/lib/utils'
import { TEST_CODE_GIZMO } from '@e2e/playwright/storageStates'
import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { test, expect } from './zoo-test'
import { getUtils } from './test-utils'
import { uuidv4 } from 'lib/utils'
import { TEST_CODE_GIZMO } from './storageStates'
test.describe('Testing Gizmo', { tag: ['@skipWin'] }, () => {
const cases = [
@ -256,7 +255,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
await context.addInitScript(() => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
`
const sketch002 = startSketchOn(XZ)
|> startProfileAt([-108.83, -57.48], %)
|> angledLine([0, 105.13], %, $rectangleSegmentA001)

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