Compare commits

..

3 Commits

Author SHA1 Message Date
5ab814d153 Add debouncing settle detection 2025-04-01 13:33:49 -04:00
fe83cd94ca Add debounce function 2025-04-01 13:33:49 -04:00
5178a72c52 Add registry for background promises 2025-04-01 13:33:49 -04:00
853 changed files with 88627 additions and 76938 deletions

103
.eslintrc Normal file
View File

@ -0,0 +1,103 @@
{
"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/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()."
}
],
"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"
}
}
]
}

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

@ -1,5 +1,5 @@
name: Bug Report name: Bug Report
description: File a bug report for the Zoo Design Studio description: File a bug report for the Zoo Modeling App
title: "[BUG]: " title: "[BUG]: "
labels: ["bug"] labels: ["bug"]
assignees: [] assignees: []
@ -70,7 +70,7 @@ body:
id: version id: version
attributes: attributes:
label: Version label: Version
description: "The version of the Zoo Design Studio you're using." description: "The version of the Zoo Modeling App you're using."
placeholder: "example: v0.15.0. You can find this in the settings." placeholder: "example: v0.15.0. You can find this in the settings."
validations: validations:
required: true required: true

View File

@ -241,7 +241,7 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: out-arm64-${{ matrix.platform }} name: out-arm64-${{ matrix.platform }}
# first two will pick both Zoo Design Studio-$VERSION-arm64-win.exe and Zoo Design Studio-$VERSION-win.exe # first two will pick both Zoo Modeling App-$VERSION-arm64-win.exe and Zoo Modeling App-$VERSION-win.exe
path: | path: |
out/*-${{ env.VERSION_NO_V }}-win.* out/*-${{ env.VERSION_NO_V }}-win.*
out/*-${{ env.VERSION_NO_V }}-arm64-win.* out/*-${{ env.VERSION_NO_V }}-arm64-win.*

View File

@ -281,7 +281,7 @@ jobs:
os: os:
- "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64" - "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
- namespace-profile-macos-8-cores - namespace-profile-macos-8-cores
- windows-latest-8-cores - windows-latest
shardIndex: [1, 2, 3, 4] shardIndex: [1, 2, 3, 4]
shardTotal: [4] shardTotal: [4]
# Disable macos and windows tests on hourly e2e tests since we only care # Disable macos and windows tests on hourly e2e tests since we only care
@ -292,7 +292,7 @@ jobs:
exclude: exclude:
- os: namespace-profile-macos-8-cores - os: namespace-profile-macos-8-cores
isScheduled: true isScheduled: true
- os: windows-latest-8-cores - os: windows-latest
isScheduled: true isScheduled: true
# TODO: add ref here for main and latest release tag # TODO: add ref here for main and latest release tag
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -370,8 +370,8 @@ jobs:
with: with:
shell: bash shell: bash
command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{ env.OS_NAME }} command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{ env.OS_NAME }}
timeout_minutes: 30 timeout_minutes: 45
max_attempts: 9 max_attempts: 15
env: env:
FAIL_ON_CONSOLE_ERRORS: true FAIL_ON_CONSOLE_ERRORS: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}

View File

@ -136,36 +136,6 @@ jobs:
- run: yarn lint - 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: python-codespell:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:

View File

@ -1,27 +1,27 @@
# Setting Up Zoo Design Studio # Setting Up Zoo Modeling App
Compared to other CAD software, getting Zoo Design Studio up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install. Compared to other CAD software, getting Zoo Modeling App up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install.
## Windows ## Windows
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type. 1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
2. Once downloaded, run the installer `Zoo Design Studio-{version}-{arch}-win.exe` which should take a few seconds. 2. Once downloaded, run the installer `Zoo Modeling App-{version}-{arch}-win.exe` which should take a few seconds.
3. The installation happens at `C:\Program Files\Zoo Design Studio`. A shortcut in the start menu is also created so you can run the app easily by clicking on it. 3. The installation happens at `C:\Program Files\Zoo Modeling App`. A shortcut in the start menu is also created so you can run the app easily by clicking on it.
## macOS ## macOS
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type. 1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
2. Once downloaded, open the disk image `Zoo Design Studio-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory. 2. Once downloaded, open the disk image `Zoo Modeling App-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
3. You can then open your `Applications` directory and double-click on `Zoo Design Studio` to open. 3. You can then open your `Applications` directory and double-click on `Zoo Modeling App` to open.
## Linux ## Linux
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type. 1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/). 2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
- On Ubuntu, install the FUSE library with these commands in a terminal. - On Ubuntu, install the FUSE library with these commands in a terminal.
@ -30,7 +30,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
sudo apt install libfuse2 sudo apt install libfuse2
``` ```
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless. - Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
- Once installed, copy the downloaded `Zoo Design Studio-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`. - Once installed, copy the downloaded `Zoo Modeling App-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
- `appimaged` should automatically find it and make it executable. If not, run: - `appimaged` should automatically find it and make it executable. If not, run:
```bash ```bash

View File

@ -4,38 +4,18 @@ all: install build check
############################################################################### ###############################################################################
# INSTALL # 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 WASM_PACK ?= ~/.cargo/bin/wasm-pack
endif
.PHONY: install .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 node_modules/.yarn-integrity: package.json yarn.lock
yarn install yarn install
ifeq ($(OS),Windows_NT)
@ type nul > $@
else
@ touch $@ @ touch $@
endif
$(CARGO):
ifeq ($(OS),Windows_NT)
yarn install:rust:windows
else
yarn install:rust
endif
$(WASM_PACK): $(WASM_PACK):
ifeq ($(OS),Windows_NT) yarn install:rust
yarn install:wasm-pack:cargo
else
yarn install:wasm-pack:sh yarn install:wasm-pack:sh
endif
############################################################################### ###############################################################################
# BUILD # BUILD
@ -51,17 +31,13 @@ VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
build: build-web build-desktop build: build-web build-desktop
.PHONY: build-web .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 .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) public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES)$(RUST_SOURCES)
ifeq ($(OS),Windows_NT)
yarn build:wasm:dev:windows
else
yarn build:wasm:dev yarn build:wasm:dev
endif
build/index.html: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES) build/index.html: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
yarn build:local yarn build:local
@ -87,10 +63,8 @@ lint: install ## Lint the code
############################################################################### ###############################################################################
# RUN # RUN
TARGET ?= desktop
.PHONY: run .PHONY: run
run: run-$(TARGET) run: run-web
.PHONY: run-web .PHONY: run-web
run-web: install build-web ## Start the web app run-web: install build-web ## Start the web app
@ -103,9 +77,9 @@ run-desktop: install build-desktop ## Start the desktop app
############################################################################### ###############################################################################
# TEST # TEST
E2E_GREP ?= E2E_WORKERS ?= 1
E2E_WORKERS ?=
E2E_FAILURES ?= 1 E2E_FAILURES ?= 1
E2E_GREP ?= ""
.PHONY: test .PHONY: test
test: test-unit test-e2e test: test-unit test-e2e
@ -116,47 +90,31 @@ test-unit: install ## Run the unit tests
yarn test:unit yarn test:unit
.PHONY: test-e2e .PHONY: test-e2e
test-e2e: test-e2e-$(TARGET) test-e2e: test-e2e-desktop
.PHONY: test-e2e-web .PHONY: test-e2e-web
test-e2e-web: install build-web ## Run the web e2e tests test-e2e-web: install build-web ## Run the web e2e tests
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 ) @ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
ifdef E2E_GREP yarn chrome:test --headed --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep=$(E2E_GREP)
yarn chrome:test --headed --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
else
yarn chrome:test --headed --workers='100%'
endif
.PHONY: test-e2e-desktop .PHONY: test-e2e-desktop
test-e2e-desktop: install build-desktop ## Run the desktop e2e tests test-e2e-desktop: install build-desktop ## Run the desktop e2e tests
ifdef E2E_GREP yarn test:playwright:electron --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep="$(E2E_GREP)"
yarn test:playwright:electron --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
else
yarn test:playwright:electron --workers='100%'
endif
############################################################################### ###############################################################################
# CLEAN # CLEAN
.PHONY: clean .PHONY: clean
clean: ## Delete all artifacts clean: ## Delete all artifacts
ifeq ($(OS),Windows_NT)
git clean --force -d -X
else
rm -rf .vite/ build/ rm -rf .vite/ build/
rm -rf trace.zip playwright-report/ test-results/ rm -rf trace.zip playwright-report/ test-results/
rm -rf public/kcl_wasm_lib_bg.wasm rm -rf public/kcl_wasm_lib_bg.wasm
rm -rf rust/*/bindings/ rust/*/pkg/ rust/target/ rm -rf rust/*/bindings/ rust/*/pkg/ rust/target/
rm -rf node_modules/ rust/*/node_modules/ rm -rf node_modules/ rust/*/node_modules/
endif
.PHONY: help .PHONY: help
help: install 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}' @ grep -E '^[^[:space:]]+:.*## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
endif
.DEFAULT_GOAL := help .DEFAULT_GOAL := help

View File

@ -1,17 +1,17 @@
![Zoo Design Studio](/public/zma-logomark-outlined.png) ![Zoo Modeling App](/public/zma-logomark-outlined.png)
## Zoo Design Studio ## Zoo Modeling App
download at [zoo.dev/modeling-app/download](https://zoo.dev/modeling-app/download) download at [zoo.dev/modeling-app/download](https://zoo.dev/modeling-app/download)
A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev). A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
Design Studio is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence: Modeling App is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence:
- All artifacts—including parts and assemblies—should be represented as human-readable code. At the end of the day, your CAD project should be "plain text" - All artifacts—including parts and assemblies—should be represented as human-readable code. At the end of the day, your CAD project should be "plain text"
- This makes version control—which is a solved problem in software engineering—trivial for CAD - This makes version control—which is a solved problem in software engineering—trivial for CAD
- All GUI (or point-and-click) interactions should be actions performed on this code representation under the hood - All GUI (or point-and-click) interactions should be actions performed on this code representation under the hood
- This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in Design Studio - This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in Modeling App
- Everything graphics _has_ to be built for the GPU - Everything graphics _has_ to be built for the GPU
- Most CAD applications have had to retrofit support for GPUs, but our geometry engine is made for GPUs (primarily Nvidia's Vulkan), getting the order of magnitude rendering performance boost with it - Most CAD applications have had to retrofit support for GPUs, but our geometry engine is made for GPUs (primarily Nvidia's Vulkan), getting the order of magnitude rendering performance boost with it
- Make the resource-intensive pieces of an application auto-scaling - Make the resource-intensive pieces of an application auto-scaling
@ -19,9 +19,9 @@ Design Studio is our take on what a modern modelling experience can be. It is ap
We are excited about what a small team of people could build in a short time with our API. We welcome you to try our API, build your own applications, or contribute to ours! We are excited about what a small team of people could build in a short time with our API. We welcome you to try our API, build your own applications, or contribute to ours!
Design Studio is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more. Modeling App is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more.
The 3D view in Design Studio is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine. The 3D view in Modeling App is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine.
## Tools ## Tools
@ -198,13 +198,13 @@ If the prompt doesn't show up, start the app in command line to grab the electro
``` ```
# Windows (PowerShell) # Windows (PowerShell)
& 'C:\Program Files\Zoo Design Studio\Zoo Design Studio.exe' & 'C:\Program Files\Zoo Modeling App\Zoo Modeling App.exe'
# macOS # macOS
/Applications/Zoo\ Modeling\ App.app/Contents/MacOS/Zoo\ Modeling\ App /Applications/Zoo\ Modeling\ App.app/Contents/MacOS/Zoo\ Modeling\ App
# Linux # Linux
./Zoo Design Studio-{version}-{arch}-linux.AppImage ./Zoo Modeling App-{version}-{arch}-linux.AppImage
``` ```
#### 4. Publish the release #### 4. Publish the release

View File

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

View File

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

View File

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

View File

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

View File

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

127
docs/kcl/helix.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@ Import a CAD file.
For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory. For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.
Note: The import command currently only works when using the native Design Studio. Note: The import command currently only works when using the native Modeling App.
```js ```js
import( import(

View File

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

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
--- ---
title: "KCL Known Issues" title: "KCL Known Issues"
excerpt: "Known issues with the KCL standard library for the Zoo Design Studio." excerpt: "Known issues with the KCL standard library for the Zoo Modeling App."
layout: manual layout: manual
--- ---

104
docs/kcl/mirror2d.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
--- ---
title: "KCL Modules" title: "KCL Modules"
excerpt: "Documentation of modules for the KCL language for the Zoo Design Studio." excerpt: "Documentation of modules for the KCL language for the Zoo Modeling App."
layout: manual layout: manual
--- ---
@ -95,7 +95,7 @@ import "tests/inputs/cube.obj"
When importing a GLTF file, the bin file will be imported as well. When importing a GLTF file, the bin file will be imported as well.
Import paths are relative to the current project directory. Imports currently only work when Import paths are relative to the current project directory. Imports currently only work when
using the native Design Studio, not in the browser. using the native Modeling App, not in the browser.
### Supported values ### Supported values

237
docs/kcl/revolve.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,12 @@
--- ---
title: "KCL Settings" title: "KCL Settings"
excerpt: "Documentation of settings for the KCL language and Zoo Design Studio." excerpt: "Documentation of settings for the KCL language and Zoo Modeling App."
layout: manual layout: manual
--- ---
# KCL Settings # KCL Settings
There are three levels of settings available in the KittyCAD Design Studiolication: There are three levels of settings available in the KittyCAD modeling application:
1. [User Settings](/docs/kcl/settings/user): Global settings that apply to all projects, stored in `user.toml` 1. [User Settings](/docs/kcl/settings/user): Global settings that apply to all projects, stored in `user.toml`
2. [Project Settings](/docs/kcl/settings/project): Settings specific to a project, stored in `project.toml` 2. [Project Settings](/docs/kcl/settings/project): Settings specific to a project, stored in `project.toml`
@ -14,7 +14,7 @@ There are three levels of settings available in the KittyCAD Design Studiolicati
## Configuration Files ## Configuration Files
The KittyCAD Design Studio uses TOML files for configuration: The KittyCAD modeling app uses TOML files for configuration:
* **User Settings**: `user.toml` - See [complete documentation](/docs/kcl/settings/user) * **User Settings**: `user.toml` - See [complete documentation](/docs/kcl/settings/user)
* **Project Settings**: `project.toml` - See [complete documentation](/docs/kcl/settings/project) * **Project Settings**: `project.toml` - See [complete documentation](/docs/kcl/settings/project)

View File

@ -35,7 +35,7 @@ base_unit = "in"
#### app #### app
The settings for the Design Studio. The settings for the modeling app.
**Default:** None **Default:** None

View File

@ -37,7 +37,7 @@ text_wrapping = false
#### app #### app
The settings for the Design Studio. The settings for the modeling app.
**Default:** None **Default:** None

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
--- ---
title: "KCL Types" title: "KCL Types"
excerpt: "Documentation of types for the KCL standard library for the Zoo Design Studio." excerpt: "Documentation of types for the KCL standard library for the Zoo Modeling App."
layout: manual layout: manual
--- ---

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

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.describe('Electron app header tests', () => {
test( test(

View File

@ -1,14 +1,13 @@
import type { Page } from '@playwright/test' import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
import { import {
PERSIST_MODELING_CONTEXT, getUtils,
TEST_COLORS, TEST_COLORS,
commonPoints, commonPoints,
getUtils, PERSIST_MODELING_CONTEXT,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils' } from './test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { HomePageFixture } from './fixtures/homePageFixture'
test.setTimeout(120000) test.setTimeout(120000)

View File

@ -1,8 +1,7 @@
import { test, expect } from './zoo-test'
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
import path from 'node:path' import path from 'node:path'
import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Point and click for boolean workflows', () => { test.describe('Point and click for boolean workflows', () => {
// Boolean operations to test // Boolean operations to test
const booleanOperations = [ const booleanOperations = [

View File

@ -1,11 +1,10 @@
import type { Page } from '@playwright/test' import { Page } from '@playwright/test'
import type { EngineCommand } from '@src/lang/std/artifactGraph' import { test, expect } from './zoo-test'
import { uuidv4 } from '@src/lib/utils' import { HomePageFixture } from './fixtures/homePageFixture'
import { getUtils } from './test-utils'
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture' import { EngineCommand } from 'lang/std/artifactGraph'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture' import { uuidv4 } from 'lib/utils'
import { getUtils } from '@e2e/playwright/test-utils' import { SceneFixture } from './fixtures/sceneFixture'
import { expect, test } from '@e2e/playwright/zoo-test'
test.describe( test.describe(
'Can create sketches on all planes and their back sides', 'Can create sketches on all planes and their back sides',

View File

@ -1,14 +1,13 @@
import { bracket } from '@src/lib/exampleKcl' import { test, expect } from './zoo-test'
import fsp from 'fs/promises'
import { join } from 'path'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from '@e2e/playwright/storageStates'
import { import {
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils' getUtils,
import { expect, test } from '@e2e/playwright/zoo-test' 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.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
test('Typing KCL errors induces a badge on the code pane button', async ({ test('Typing KCL errors induces a badge on the code pane button', async ({

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

View File

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

View File

@ -1,5 +1,6 @@
import { getUtils } from '@e2e/playwright/test-utils' import { test, expect } from './zoo-test'
import { expect, test } from '@e2e/playwright/zoo-test'
import { getUtils } from './test-utils'
function countNewlines(input: string): number { function countNewlines(input: string): number {
let count = 0 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 path from 'path'
import { import {
getUtils,
executorInputPath, executorInputPath,
getPlaywrightDownloadDir, getPlaywrightDownloadDir,
getUtils, } from './test-utils'
} from '@e2e/playwright/test-utils' import fsp from 'fs/promises'
import { expect, test } from '@e2e/playwright/zoo-test'
test( test(
'export works on the first try', 'export works on the first try',
@ -32,44 +31,57 @@ test(
}) })
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await test.step('on open of project', async () => { page.on('console', console.log)
// Open the project
const projectName = page.getByText(`bracket`)
await expect(projectName).toBeVisible()
await projectName.click()
await scene.waitForExecutionDone()
await page.waitForTimeout(1_000) // wait for panel buttons to be available
// Expect zero errors in gutter await test.step('on open of project', async () => {
await expect(page.getByText(`bracket`)).toBeVisible()
// open the project
await page.getByText(`bracket`).click()
// expect zero errors in guter
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// Click the export button // export the model
const exportButton = page.getByTestId('export-pane-button') const exportButton = page.getByTestId('export-pane-button')
await expect(exportButton).toBeVisible() await expect(exportButton).toBeVisible()
await exportButton.click()
await page.waitForTimeout(1_000) // wait for export options to be available
// Select the first format option // Wait for the model to finish loading
const modelStateIndicator = page.getByTestId(
'model-state-indicator-execution-done'
)
await expect(modelStateIndicator).toBeVisible({ timeout: 60000 })
const gltfOption = page.getByText('glTF') const gltfOption = page.getByText('glTF')
const exportFileName = `main.gltf` // source file is named `main.kcl` const submitButton = page.getByText('Confirm Export')
const exportingToastMessage = page.getByText(`Exporting...`)
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// The open file's name is `main.kcl`, so the export file name should be `main.gltf`
const exportFileName = `main.gltf`
// Click the export button
await exportButton.click()
await expect(gltfOption).toBeVisible() await expect(gltfOption).toBeVisible()
await expect(page.getByText('STL')).toBeVisible()
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Click the checkbox // Click the checkbox
const submitButton = page.getByText('Confirm Export')
await expect(submitButton).toBeVisible() await expect(submitButton).toBeVisible()
await page.waitForTimeout(500) await page.waitForTimeout(500)
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Find the toast.
// Look out for the toast message // Look out for the toast message
const exportingToastMessage = page.getByText(`Exporting...`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
await expect(exportingToastMessage).toBeVisible() await expect(exportingToastMessage).toBeVisible()
await expect(alreadyExportingToastMessage).not.toBeVisible() await expect(alreadyExportingToastMessage).not.toBeVisible()
// Expect it to succeed // Expect it to succeed.
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
await expect(errorToastMessage).not.toBeVisible() await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible() await expect(engineErrorToastMessage).not.toBeVisible()
@ -77,7 +89,6 @@ test(
await expect(successToastMessage).toBeVisible() await expect(successToastMessage).toBeVisible()
await expect(exportingToastMessage).not.toBeVisible() await expect(exportingToastMessage).not.toBeVisible()
// Check for the exported file
const firstFileFullPath = path.resolve( const firstFileFullPath = path.resolve(
getPlaywrightDownloadDir(tronApp.projectDirName), getPlaywrightDownloadDir(tronApp.projectDirName),
exportFileName exportFileName
@ -104,53 +115,60 @@ test(
const u = await getUtils(page) const u = await getUtils(page)
await u.openFilePanel() await u.openFilePanel()
// Click on the other file
const otherKclButton = page.getByRole('button', { name: 'other.kcl' }) const otherKclButton = page.getByRole('button', { name: 'other.kcl' })
// Click the file
await otherKclButton.click() await otherKclButton.click()
// Close the file pane // Close the file pane
await u.closeFilePanel() await u.closeFilePanel()
await scene.waitForExecutionDone()
await page.waitForTimeout(1_000) // wait for panel buttons to be available
// Expect zero errors in gutter // FIXME: await scene.waitForExecutionDone() does not work. The modeling indicator stays in -receive-reliable and not execution done
await page.waitForTimeout(10000)
// expect zero errors in guter
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// Click the export button // export the model
const exportButton = page.getByTestId('export-pane-button') const exportButton = page.getByTestId('export-pane-button')
await expect(exportButton).toBeVisible() await expect(exportButton).toBeVisible()
await exportButton.click()
await page.waitForTimeout(1_000) // wait for export options to be available
// Select the first format option
const gltfOption = page.getByText('glTF') const gltfOption = page.getByText('glTF')
const exportFileName = `other.gltf` // source file is named `other.kcl` const submitButton = page.getByText('Confirm Export')
const exportingToastMessage = page.getByText(`Exporting...`)
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// The open file's name is `other.kcl`, so the export file name should be `other.gltf`
const exportFileName = `other.gltf`
// Click the export button
await exportButton.click()
await expect(gltfOption).toBeVisible() await expect(gltfOption).toBeVisible()
await expect(page.getByText('STL')).toBeVisible()
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Click the checkbox // Click the checkbox
const submitButton = page.getByText('Confirm Export')
await expect(submitButton).toBeVisible() await expect(submitButton).toBeVisible()
await page.waitForTimeout(500)
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Find the toast.
// Look out for the toast message // Look out for the toast message
const exportingToastMessage = page.getByText(`Exporting...`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
await expect(exportingToastMessage).toBeVisible() await expect(exportingToastMessage).toBeVisible()
await expect(alreadyExportingToastMessage).not.toBeVisible()
// Expect it to succeed
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`) const successToastMessage = page.getByText(`Exported successfully`)
await expect(successToastMessage).toBeVisible() await test.step('Check the success toast message shows and nothing else', async () =>
await expect(exportingToastMessage).not.toBeVisible() Promise.all([
expect(alreadyExportingToastMessage).not.toBeVisible(),
expect(errorToastMessage).not.toBeVisible(),
expect(engineErrorToastMessage).not.toBeVisible(),
expect(successToastMessage).toBeVisible(),
expect(exportingToastMessage).not.toBeVisible(),
]))
// Check for the exported file=
const secondFileFullPath = path.resolve( const secondFileFullPath = path.resolve(
getPlaywrightDownloadDir(tronApp.projectDirName), getPlaywrightDownloadDir(tronApp.projectDirName),
exportFileName exportFileName

View File

@ -1,14 +1,14 @@
import { uuidv4 } from '@src/lib/utils' import { test, expect } from './zoo-test'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import { join } from 'path' import { uuidv4 } from 'lib/utils'
import { import {
TEST_COLORS,
executorInputPath, executorInputPath,
getUtils, getUtils,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils' TEST_COLORS,
import { expect, test } from '@e2e/playwright/zoo-test' } from './test-utils'
import { join } from 'path'
test.describe('Editor tests', { tag: ['@skipWin'] }, () => { test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
test('can comment out code with ctrl+/', async ({ page, homePage }) => { test('can comment out code with ctrl+/', async ({ page, homePage }) => {
@ -985,13 +985,12 @@ sketch001 = startSketchOn(XZ)
test( test(
'Can undo a sketch modification with ctrl+z', 'Can undo a sketch modification with ctrl+z',
{ tag: ['@skipWin'] }, { tag: ['@skipWin'] },
async ({ page, homePage, editor }) => { async ({ page, homePage }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`@settings(defaultLengthUnit=in) `sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %) |> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09]) |> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -0.38], %)
@ -1081,45 +1080,41 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent) await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed // expect the code to have changed
await editor.expectEditor.toContain( await expect(page.locator('.cm-content'))
`sketch001 = startSketchOn(XZ) .toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %) |> startProfileAt([2.71, -2.71], %)
|> line(end = [15.4, -2.78]) |> line(end = [15.4, -2.78])
|> tangentialArcTo([27.6, -3.05], %) |> tangentialArcTo([27.6, -3.05], %)
|> close() |> close()
|> extrude(length = 5)`, |> extrude(length = 5)
{ shouldNormalise: true } `)
)
// Hit undo // Hit undo
await page.keyboard.down('Control') await page.keyboard.down('Control')
await page.keyboard.press('KeyZ') await page.keyboard.press('KeyZ')
await page.keyboard.up('Control') await page.keyboard.up('Control')
await editor.expectEditor.toContain( await expect(page.locator('.cm-content'))
`sketch001 = startSketchOn(XZ) .toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %) |> startProfileAt([2.71, -2.71], %)
|> line(end = [15.4, -2.78]) |> line(end = [15.4, -2.78])
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -0.38], %)
|> close() |> close()
|> extrude(length = 5)`, |> extrude(length = 5)`)
{ shouldNormalise: true }
)
// Hit undo again. // Hit undo again.
await page.keyboard.down('Control') await page.keyboard.down('Control')
await page.keyboard.press('KeyZ') await page.keyboard.press('KeyZ')
await page.keyboard.up('Control') await page.keyboard.up('Control')
await editor.expectEditor.toContain( await expect(page.locator('.cm-content'))
`sketch001 = startSketchOn(XZ) .toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([2.71, -2.71], %) |> startProfileAt([2.71, -2.71], %)
|> line(end = [12.73, -0.09]) |> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -0.38], %)
|> close() |> close()
|> extrude(length = 5)`, |> extrude(length = 5)
{ shouldNormalise: true } `)
)
// Hit undo again. // Hit undo again.
await page.keyboard.down('Control') await page.keyboard.down('Control')
@ -1127,15 +1122,13 @@ sketch001 = startSketchOn(XZ)
await page.keyboard.up('Control') await page.keyboard.up('Control')
await page.waitForTimeout(100) await page.waitForTimeout(100)
await editor.expectEditor.toContain( await expect(page.locator('.cm-content'))
`sketch001 = startSketchOn(XZ) .toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %) |> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09]) |> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -0.38], %)
|> close() |> close()
|> extrude(length = 5)`, |> extrude(length = 5)`)
{ shouldNormalise: true }
)
} }
) )
@ -1274,79 +1267,4 @@ sketch001 = startSketchOn(XZ)
await middleMousePan(800, 200, 900, 300) await middleMousePan(800, 200, 900, 300)
}) })
}) })
test('Can select lines on the main axis', async ({
page,
homePage,
toolbar,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([100.00, 100.0], sketch001)
|> yLine(length = -100.0)
|> xLine(length = 200.0)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`
)
})
const width = 1200
const height = 800
const viewportSize = { width, height }
await page.setBodyDimensions(viewportSize)
await homePage.goToModelingScene()
const u = await getUtils(page)
await u.waitForPageLoad()
await toolbar.editSketch(0)
await page.waitForTimeout(1000)
// Click on the bottom segment that lies on the x axis
await page.mouse.click(width * 0.85, height / 2)
await page.waitForTimeout(1000)
// Verify segment is selected (you can check for visual indicators or state)
const element = page.locator('[data-overlay-index="1"]')
await expect(element).toHaveAttribute('data-overlay-visible', 'true')
})
test(`Only show axis planes when there are no errors`, async ({
page,
homePage,
scene,
cmdBar,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`sketch001 = startSketchOn(XZ)
profile001 = circle(sketch001, center = [-100.0, -100.0], radius = 50.0)
sketch002 = startSketchOn(XZ)
profile002 = circle(sketch002, center = [-100.0, 100.0], radius = 50.0)
extrude001 = extrude(profile002, length = 0)` // length = 0 is causing the error
)
})
const viewportSize = { width: 1200, height: 800 }
await page.setBodyDimensions(viewportSize)
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
await scene.expectPixelColor(
TEST_COLORS.DARK_MODE_BKGD,
// This is a position where the blue part of the axis plane is visible if its rendered
{ x: viewportSize.width * 0.75, y: viewportSize.height * 0.2 },
15
)
})
}) })

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import type { Locator, Page, Request, Route, TestInfo } from '@playwright/test' import type { Page, Locator, Route, Request } from '@playwright/test'
import { expect } from '@playwright/test' import { expect, TestInfo } from '@playwright/test'
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' 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 { expect } from '@playwright/test'
import { import {
checkIfPaneIsOpen,
closePane, closePane,
checkIfPaneIsOpen,
openPane, openPane,
sansWhitespace, sansWhitespace,
} from '@e2e/playwright/test-utils' } from '../test-utils'
interface EditorState { interface EditorState {
activeLines: Array<string> activeLines: Array<string>

View File

@ -1,28 +1,28 @@
/* eslint-disable react-hooks/rules-of-hooks */ /* eslint-disable react-hooks/rules-of-hooks */
import type { import type {
BrowserContext, BrowserContext,
ElectronApplication, ElectronApplication,
Page,
TestInfo, TestInfo,
Page,
} from '@playwright/test' } from '@playwright/test'
import { _electron as electron } from '@playwright/test' import { _electron as electron } from '@playwright/test'
import { SETTINGS_FILE_NAME } from '@src/lib/constants' import * as TOML from '@iarna/toml'
import type { DeepPartial } from '@src/lib/types' import { TEST_SETTINGS } from '../storageStates'
import { SETTINGS_FILE_NAME } from 'lib/constants'
import { getUtils, setup } from '../test-utils'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import fs from 'node:fs' import fs from 'node:fs'
import path from 'path' import path from 'path'
import { CmdBarFixture } from './cmdBarFixture'
import type { Settings } from '@rust/kcl-lib/bindings/Settings' import { EditorFixture } from './editorFixture'
import { ToolbarFixture } from './toolbarFixture'
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture' import { SceneFixture } from './sceneFixture'
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture' import { HomePageFixture } from './homePageFixture'
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture' import { DeepPartial } from 'lib/types'
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture' import { Settings } from '@rust/kcl-lib/bindings/Settings'
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import { TEST_SETTINGS } from '@e2e/playwright/storageStates'
import { getUtils, settingsToToml, setup } from '@e2e/playwright/test-utils'
export class AuthenticatedApp { export class AuthenticatedApp {
public readonly page: Page public readonly page: Page
@ -177,25 +177,6 @@ export class ElectronZoo {
this.context = this.electron.context() this.context = this.electron.context()
await this.context.tracing.start({ screenshots: true, snapshots: true }) await this.context.tracing.start({ screenshots: true, snapshots: true })
// We need to patch this because addInitScript will bind too late in our
// electron tests, never running. We need to call reload() after each call
// to guarantee it runs.
const oldContextAddInitScript = this.context.addInitScript
this.context.addInitScript = async function (a, b) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldContextAddInitScript.apply(this, [a, b])
await that.page.reload()
}
const oldPageAddInitScript = this.page.addInitScript
this.page.addInitScript = async function (a: any, b: any) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldPageAddInitScript.apply(this, [a, b])
await that.page.reload()
}
} }
await this.context.tracing.startChunk() await this.context.tracing.startChunk()
@ -238,6 +219,26 @@ export class ElectronZoo {
})) }))
} }
// We need to patch this because addInitScript will bind too late in our
// electron tests, never running. We need to call reload() after each call
// to guarantee it runs.
const oldContextAddInitScript = this.context.addInitScript
this.context.addInitScript = async function (a, b) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldContextAddInitScript.apply(this, [a, b])
await that.page.reload()
}
// No idea why we mix and match page and context's addInitScript but we do
const oldPageAddInitScript = this.page.addInitScript
this.page.addInitScript = async function (a: any, b: any) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldPageAddInitScript.apply(this, [a, b])
await that.page.reload()
}
if (!this.firstUrl) { if (!this.firstUrl) {
await this.page.getByText('Your Projects').count() await this.page.getByText('Your Projects').count()
this.firstUrl = this.page.url() this.firstUrl = this.page.url()
@ -286,30 +287,26 @@ export class ElectronZoo {
let settingsOverridesToml = '' let settingsOverridesToml = ''
if (appSettings) { if (appSettings) {
settingsOverridesToml = settingsToToml({ settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: { settings: {
...TEST_SETTINGS, ...TEST_SETTINGS,
...appSettings, ...appSettings,
app: { app: {
...TEST_SETTINGS.app, ...TEST_SETTINGS.app,
project_directory: this.projectDirName,
...appSettings.app, ...appSettings.app,
}, },
project: {
...TEST_SETTINGS.project,
directory: this.projectDirName,
},
}, },
}) })
} else { } else {
settingsOverridesToml = settingsToToml({ settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: { settings: {
...TEST_SETTINGS, ...TEST_SETTINGS,
app: { app: {
...TEST_SETTINGS.app, ...TEST_SETTINGS.app,
}, project_directory: this.projectDirName,
project: {
...TEST_SETTINGS.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' import { expect } from '@playwright/test'
interface ProjectCardState { interface ProjectCardState {
@ -96,7 +96,7 @@ export class HomePageFixture {
await expect(this.projectSection).not.toHaveText('Loading your Projects...') await expect(this.projectSection).not.toHaveText('Loading your Projects...')
} }
createAndGoToProject = async (projectTitle = 'untitled') => { createAndGoToProject = async (projectTitle = 'project-$nnn') => {
await this.projectsLoaded() await this.projectsLoaded()
await this.projectButtonNew.click() await this.projectButtonNew.click()
await this.projectTextName.click() await this.projectTextName.click()

View File

@ -1,17 +1,15 @@
import type { Locator, Page } from '@playwright/test' import type { Page, Locator } from '@playwright/test'
import { isArray, uuidv4 } from '@src/lib/utils' import { expect } from '../zoo-test'
import { isArray, uuidv4 } from 'lib/utils'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture' import { CmdBarFixture } from './cmdBarFixture'
import { import {
closeDebugPanel, closeDebugPanel,
doAndWaitForImageDiff, doAndWaitForImageDiff,
getPixelRGBs, getPixelRGBs,
getUtils,
openAndClearDebugPanel, openAndClearDebugPanel,
sendCustomCmd, sendCustomCmd,
} from '@e2e/playwright/test-utils' getUtils,
import { expect } from '@e2e/playwright/zoo-test' } from '../test-utils'
type MouseParams = { type MouseParams = {
pixelDiff?: number pixelDiff?: number

View File

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

View File

@ -1,120 +0,0 @@
import { expect, test } from '@e2e/playwright/zoo-test'
import * as fsp from 'fs/promises'
import path from 'path'
test.describe('Import UI tests', () => {
test('shows toast when trying to sketch on imported face', async ({
context,
page,
homePage,
toolbar,
scene,
editor,
}) => {
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, 'import-test')
await fsp.mkdir(projectDir, { recursive: true })
// Create the imported file
await fsp.writeFile(
path.join(projectDir, 'toBeImported.kcl'),
`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([281.54, 305.81], sketch001)
|> angledLine([0, 123.43], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
85.99
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude(profile001, length = 100)`
)
// Create the main file that imports
await fsp.writeFile(
path.join(projectDir, 'main.kcl'),
`import "toBeImported.kcl" as importedCube
importedCube
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([-134.53, -56.17], sketch001)
|> angledLine([0, 79.05], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
76.28
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(profile001, length = 100)
sketch003 = startSketchOn(extrude001, seg02)
sketch002 = startSketchOn(extrude001, seg01)`
)
})
await homePage.openProject('import-test')
await scene.waitForExecutionDone()
await scene.moveCameraTo(
{
x: -114,
y: -897,
z: 475,
},
{
x: -114,
y: -51,
z: 83,
}
)
const [_, hoverOverNonImport] = scene.makeMouseHelpers(611, 364)
const [importedFaceClick, hoverOverImported] = scene.makeMouseHelpers(
940,
150
)
await test.step('check code highlight works for code define in the file being edited', async () => {
await hoverOverNonImport()
await editor.expectState({
highlightedCode: 'startProfileAt([-134.53,-56.17],sketch001)',
diagnostics: [],
activeLines: ['import"toBeImported.kcl"asimportedCube'],
})
})
await test.step('check code does nothing when geometry is defined in an import', async () => {
await hoverOverImported()
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: ['import"toBeImported.kcl"asimportedCube'],
})
})
await test.step('check the user is warned when sketching on a imported face', async () => {
// Start sketch mode
await toolbar.startSketchPlaneSelection()
// Click on a face from the imported model
// await new Promise(() => {})
await importedFaceClick()
// Verify toast appears with correct content
await expect(page.getByText('This face is from an import')).toBeVisible()
await expect(
page.locator('.font-mono').getByText('toBeImported.kcl')
).toBeVisible()
await expect(
page.getByText('Please select this from the files pane to edit')
).toBeVisible()
})
})
})

View File

@ -1,7 +0,0 @@
export const throwError = (message: string): never => {
throw new Error(message)
}
export const throwTronAppMissing = () => {
throwError('tronApp is missing')
}

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 { join } from 'path'
import fsp from 'fs/promises'
import { executorInputPath } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
test( test(
'When machine-api server not found butt is disabled and shows the reason', 'When machine-api server not found butt is disabled and shows the reason',

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 * as fsp from 'fs/promises'
import { join } from 'path' import { join } from 'path'
import type { NamedView } from '@rust/kcl-lib/bindings/NamedView'
import { import {
createProject, createProject,
perProjectsettingsToToml,
tomlToPerProjectSettings, tomlToPerProjectSettings,
} from '@e2e/playwright/test-utils' perProjectsettingsToToml,
import { expect, test } from '@e2e/playwright/zoo-test' } from './test-utils'
import { NamedView } from '@rust/kcl-lib/bindings/NamedView'
// Helper function to determine if the file path on disk exists // Helper function to determine if the file path on disk exists
// Specifically this is used to check if project.toml exists on disk // Specifically this is used to check if project.toml exists on disk

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,23 +1,22 @@
import { bracket } from '@src/lib/exampleKcl' import { test, expect } from './zoo-test'
import { onboardingPaths } from '@src/routes/Onboarding/paths'
import fsp from 'fs/promises'
import { join } from 'path' import { join } from 'path'
import fsp from 'fs/promises'
import { expectPixelColor } from '@e2e/playwright/fixtures/sceneFixture' import {
getUtils,
executorInputPath,
createProject,
settingsToToml,
orRunWhenFullSuiteEnabled,
} from './test-utils'
import { bracket } from 'lib/exampleKcl'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { import {
TEST_SETTINGS_KEY, TEST_SETTINGS_KEY,
TEST_SETTINGS_ONBOARDING_EXPORT,
TEST_SETTINGS_ONBOARDING_START, TEST_SETTINGS_ONBOARDING_START,
TEST_SETTINGS_ONBOARDING_EXPORT,
TEST_SETTINGS_ONBOARDING_USER_MENU, TEST_SETTINGS_ONBOARDING_USER_MENU,
} from '@e2e/playwright/storageStates' } from './storageStates'
import { import { expectPixelColor } from './fixtures/sceneFixture'
createProject,
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
settingsToToml,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
// Because our default test settings have the onboardingStatus set to 'dismissed', // 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. // we must set it to empty for the tests where we want to see the onboarding immediately.
@ -42,10 +41,10 @@ test.describe('Onboarding tests', () => {
await homePage.goToModelingScene() await homePage.goToModelingScene()
// Test that the onboarding pane loaded // Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Design Studio! This')).toBeVisible() await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
// Test that the onboarding pane loaded // Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Design Studio! This')).toBeVisible() await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
// *and* that the code is shown in the editor // *and* that the code is shown in the editor
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
@ -86,7 +85,7 @@ test.describe('Onboarding tests', () => {
await test.step(`Ensure we see the onboarding stuff`, async () => { await test.step(`Ensure we see the onboarding stuff`, async () => {
// Test that the onboarding pane loaded // Test that the onboarding pane loaded
await expect( await expect(
page.getByText('Welcome to Design Studio! This') page.getByText('Welcome to Modeling App! This')
).toBeVisible() ).toBeVisible()
// *and* that the code is shown in the editor // *and* that the code is shown in the editor
@ -147,7 +146,7 @@ test.describe('Onboarding tests', () => {
await nextButton.click() await nextButton.click()
// Ensure we see the introduction and that the code has been reset // Ensure we see the introduction and that the code has been reset
await expect(page.getByText('Welcome to Design Studio!')).toBeVisible() await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
// There used to be old code here that checked if we stored the reset // There used to be old code here that checked if we stored the reset
@ -188,7 +187,7 @@ test.describe('Onboarding tests', () => {
await homePage.goToModelingScene() await homePage.goToModelingScene()
// Test that the onboarding pane loaded // Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Design Studio! This')).toBeVisible() await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
const nextButton = page.getByTestId('onboarding-next') const nextButton = page.getByTestId('onboarding-next')
const prevButton = page.getByTestId('onboarding-prev') const prevButton = page.getByTestId('onboarding-prev')
@ -494,7 +493,7 @@ test('Restarting onboarding on desktop takes one attempt', async ({
const tutorialProjectIndicator = page const tutorialProjectIndicator = page
.getByTestId('project-sidebar-toggle') .getByTestId('project-sidebar-toggle')
.filter({ hasText: 'Tutorial Project 00' }) .filter({ hasText: 'Tutorial Project 00' })
const tutorialModalText = page.getByText('Welcome to Design Studio!') const tutorialModalText = page.getByText('Welcome to Modeling App!')
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' }) const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
const userMenuButton = page.getByTestId('user-sidebar-toggle') const userMenuButton = page.getByTestId('user-sidebar-toggle')
const userMenuSettingsButton = page.getByRole('button', { const userMenuSettingsButton = page.getByRole('button', {

View File

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test' import { test, expect } from '@playwright/test'
/** @deprecated, import from ./fixtureSetup.ts instead */ /** @deprecated, import from ./fixtureSetup.ts instead */
export const _test = test 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 fs from 'node:fs/promises'
import path from 'node:path' import path from 'node:path'
import { getUtils, orRunWhenFullSuiteEnabled } from './test-utils'
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture' import { Locator } from '@playwright/test'
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'
// test file is for testing point an click code gen functionality that's not sketch mode related // test file is for testing point an click code gen functionality that's not sketch mode related
@ -1084,8 +1084,8 @@ openSketch = startSketchOn(XY)
}) => { }) => {
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 } const testPoint = { x: 620, y: 257 }
const expectedOutput = `helix001 = helix( axis = X, radius = 5, length = 5, revolutions = 1, angleStart = 360, ccw = false,)` const expectedOutput = `helix001 = helix( axis = 'X', radius = 5, length = 5, revolutions = 1, angleStart = 360, ccw = false,)`
const expectedLine = `axis=X,` const expectedLine = `axis='X',`
await homePage.goToModelingScene() await homePage.goToModelingScene()
@ -1217,7 +1217,7 @@ openSketch = startSketchOn(XY)
cmdBar, cmdBar,
}) => { }) => {
page.on('console', console.log) page.on('console', console.log)
const initialCode = `sketch001 = startSketchOn(XZ) const initialCode = `sketch001 = startSketchOn('XZ')
profile001 = startProfileAt([0, 0], sketch001) profile001 = startProfileAt([0, 0], sketch001)
|> yLine(length = 100) |> yLine(length = 100)
|> line(endAbsolute = [100, 0]) |> line(endAbsolute = [100, 0])
@ -3474,7 +3474,7 @@ segAng(rectangleSegmentA002),
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
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() expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
// Edit flow // Edit flow

View File

@ -1,20 +1,19 @@
import { DEFAULT_PROJECT_KCL_FILE } from '@src/lib/constants' import { test, expect } from './zoo-test'
import fs from 'fs'
import fsp from 'fs/promises'
import path from 'path'
import type { Paths } from '@e2e/playwright/test-utils'
import { import {
createProject,
doExport, doExport,
executorInputPath, executorInputPath,
getPlaywrightDownloadDir,
getUtils, getUtils,
isOutOfViewInScrollContainer, isOutOfViewInScrollContainer,
Paths,
createProject,
getPlaywrightDownloadDir,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
runningOnWindows, runningOnWindows,
} from '@e2e/playwright/test-utils' } from './test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import fsp from 'fs/promises'
import fs from 'fs'
import path from 'path'
import { DEFAULT_PROJECT_KCL_FILE } from 'lib/constants'
test( test(
'projects reload if a new one is created, deleted, or renamed externally', 'projects reload if a new one is created, deleted, or renamed externally',
@ -828,7 +827,7 @@ test.describe(`Project management commands`, () => {
const commandButton = page.getByRole('button', { name: 'Commands' }) const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' }) const commandOption = page.getByRole('option', { name: 'rename project' })
const projectNameOption = page.getByRole('option', { name: projectName }) const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `untitled` const projectRenamedName = `project-000`
// const projectMenuButton = page.getByTestId('project-sidebar-toggle') // const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const commandContinueButton = page.getByRole('button', { const commandContinueButton = page.getByRole('button', {
name: 'Continue', name: 'Continue',
@ -941,7 +940,7 @@ test.describe(`Project management commands`, () => {
const commandButton = page.getByRole('button', { name: 'Commands' }) const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' }) const commandOption = page.getByRole('option', { name: 'rename project' })
const projectNameOption = page.getByRole('option', { name: projectName }) const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `untitled` const projectRenamedName = `project-000`
const commandContinueButton = page.getByRole('button', { const commandContinueButton = page.getByRole('button', {
name: 'Continue', name: 'Continue',
}) })
@ -1139,7 +1138,7 @@ test(`Create a few projects using the default project name`, async ({
await test.step(`Create project ${i}`, async () => { await test.step(`Create project ${i}`, async () => {
await homePage.expectState({ await homePage.expectState({
projectCards: Array.from({ length: i }, (_, i) => ({ projectCards: Array.from({ length: i }, (_, i) => ({
title: i === 0 ? 'untitled' : `untitled-${i}`, title: `project-${i.toString().padStart(3, '0')}`,
fileCount: 1, fileCount: 1,
})).toReversed(), })).toReversed(),
sortBy: 'last-modified-desc', sortBy: 'last-modified-desc',
@ -1323,9 +1322,9 @@ test(
}) })
await test.step('Check we can still create a project', async () => { await test.step('Check we can still create a project', async () => {
await createProject({ name: 'new-project', page, returnHome: true }) await createProject({ name: 'project-000', page, returnHome: true })
await expect( await expect(
page.getByTestId('project-link').filter({ hasText: 'new-project' }) page.getByTestId('project-link').filter({ hasText: 'project-000' })
).toBeVisible() ).toBeVisible()
}) })
} }

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 */ /* eslint-disable jest/no-conditional-expect */
/** /**

View File

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

View File

@ -1,18 +1,17 @@
import type { Page } from '@playwright/test' import { Page } from '@playwright/test'
import { bracket } from '@src/lib/exampleKcl' import { test, expect } from './zoo-test'
import { reportRejection } from '@src/lib/trap'
import * as fsp from 'fs/promises'
import path from 'path' import path from 'path'
import * as fsp from 'fs/promises'
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from '@e2e/playwright/storageStates'
import type { TestColor } from '@e2e/playwright/test-utils'
import { import {
TEST_COLORS,
executorInputPath,
getUtils, getUtils,
TEST_COLORS,
TestColor,
executorInputPath,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils' } from './test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' 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'] }, () => { test.describe('Regression tests', { tag: ['@skipWin'] }, () => {
// bugs we found that don't fit neatly into other categories // bugs we found that don't fit neatly into other categories
@ -798,74 +797,6 @@ plane002 = offsetPlane(XZ, offset = -2 * x)`
await page.getByTestId('custom-cmd-send-button').click() 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) { async function clickExportButton(page: Page) {

View File

@ -1,20 +1,20 @@
import type { Page } from '@playwright/test' import { Page } from '@playwright/test'
import { roundOff, uuidv4 } from '@src/lib/utils' import { test, expect } from './zoo-test'
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
import path from 'node:path' 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 { import {
PERSIST_MODELING_CONTEXT,
TEST_COLORS,
getMovementUtils, getMovementUtils,
getUtils, getUtils,
PERSIST_MODELING_CONTEXT,
TEST_COLORS,
orRunWhenFullSuiteEnabled, orRunWhenFullSuiteEnabled,
} from '@e2e/playwright/test-utils' } from './test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' 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.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
test('multi-sketch file shows multiple Edit Sketch buttons', async ({ test('multi-sketch file shows multiple Edit Sketch buttons', async ({
@ -479,8 +479,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`@settings(defaultLengthUnit=in) `sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ)
|> circle(center = [4.61, -5.01], radius = 8)` |> circle(center = [4.61, -5.01], radius = 8)`
) )
}) })
@ -565,14 +564,12 @@ sketch001 = startSketchOn(XZ)
test('Can edit a sketch that has been extruded in the same pipe', async ({ test('Can edit a sketch that has been extruded in the same pipe', async ({
page, page,
homePage, homePage,
editor,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`@settings(defaultLengthUnit=in) `sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -10.01], %) |> startProfileAt([4.61, -10.01], %)
|> line(end = [12.73, -0.09]) |> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -0.38], %)
@ -657,34 +654,31 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent) await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed // expect the code to have changed
await editor.expectEditor.toContain( await expect(page.locator('.cm-content'))
`sketch001 = startSketchOn(XZ) .toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([7.12, -12.68], %) |> startProfileAt([7.12, -12.68], %)
|> line(end = [12.68, -1.09]) |> line(end = [12.68, -1.09])
|> tangentialArcTo([24.89, 0.68], %) |> tangentialArcTo([24.89, 0.68], %)
|> close() |> close()
|> extrude(length = 5)`, |> extrude(length = 5)
{ shouldNormalise: true } `)
)
}) })
test('Can edit a sketch that has been revolved in the same pipe', async ({ test('Can edit a sketch that has been revolved in the same pipe', async ({
page, page,
homePage, homePage,
scene, scene,
editor,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`@settings(defaultLengthUnit=in) `sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -14.01], %) |> startProfileAt([4.61, -14.01], %)
|> line(end = [12.73, -0.09]) |> line(end = [12.73, -0.09])
|> tangentialArcTo([24.95, -5.38], %) |> tangentialArcTo([24.95, -5.38], %)
|> close() |> close()
|> revolve(axis = X)` |> revolve(axis = "X")`
) )
}) })
@ -764,16 +758,14 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent) await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed // expect the code to have changed
await editor.expectEditor.toContain( await expect(page.locator('.cm-content'))
`sketch001 = startSketchOn(XZ) .toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([6.44, -12.07], %) |> startProfileAt([6.44, -12.07], %)
|> line(end = [14.72, 1.97]) |> line(end = [14.72, 1.97])
|> tangentialArcTo([24.95, -5.38], %) |> tangentialArcTo([24.95, -5.38], %)
|> line(end = [1.97, 2.06]) |> line(end = [1.97, 2.06])
|> close() |> close()
|> revolve(axis = X)`, |> revolve(axis = "X")`)
{ shouldNormalise: true }
)
}) })
test('Can add multiple sketches', async ({ page, homePage }) => { test('Can add multiple sketches', async ({ page, homePage }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -1223,7 +1215,7 @@ profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
|> xLine(endAbsolute = 0 + .001) |> xLine(endAbsolute = 0 + .001)
|> yLine(endAbsolute = 0) |> yLine(endAbsolute = 0)
|> close() |> close()
|> revolve(axis = Y) |> revolve(axis = "Y")
return lugSketch return lugSketch
} }

View File

@ -1,22 +1,21 @@
import type { Models } from '@kittycad/lib' import { test, expect } from './zoo-test'
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants' import { secrets } from './secrets'
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 { import {
Paths,
doExport, doExport,
getUtils, getUtils,
orRunWhenFullSuiteEnabled,
settingsToToml, settingsToToml,
} from '@e2e/playwright/test-utils' orRunWhenFullSuiteEnabled,
import { expect, test } from '@e2e/playwright/zoo-test' } 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 }) => { test.beforeEach(async ({ page, context }) => {
// Make the user avatar image always 404 // Make the user avatar image always 404
@ -346,12 +345,10 @@ const extrudeDefaultPlane = async (
app: { app: {
onboarding_status: 'dismissed', onboarding_status: 'dismissed',
show_debug_panel: true, show_debug_panel: true,
appearance: {
theme: 'dark', theme: 'dark',
}, },
},
project: { project: {
default_project_name: 'untitled', default_project_name: 'project-$nnn',
}, },
text_editor: { text_editor: {
text_wrapping: true, text_wrapping: true,
@ -455,7 +452,7 @@ test(
await page.waitForTimeout(700) // TODO detect animation ending, or disable animation await page.waitForTimeout(700) // TODO detect animation ending, or disable animation
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) 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 expect(page.locator('.cm-content')).toHaveText(code)
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -473,7 +470,7 @@ test(
await page.waitForTimeout(500) await page.waitForTimeout(500)
code += ` code += `
|> xLine(length = 184.3)` |> xLine(length = 7.25)`
await expect(page.locator('.cm-content')).toHaveText(code) await expect(page.locator('.cm-content')).toHaveText(code)
await page await page
@ -631,7 +628,7 @@ test(
mask: [page.getByTestId('model-state-indicator')], mask: [page.getByTestId('model-state-indicator')],
}) })
await expect(page.locator('.cm-content')).toHaveText( 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 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) 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 expect(u.codeLocator).toHaveText(code)
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -676,7 +673,7 @@ test.describe(
await page.waitForTimeout(100) await page.waitForTimeout(100)
code += ` code += `
|> xLine(length = 184.3)` |> xLine(length = 7.25)`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await page await page
@ -691,7 +688,7 @@ test.describe(
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
code += ` code += `
|> tangentialArcTo([551.2, -62.01], %)` |> tangentialArcTo([21.7, -2.44], %)`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
// click tangential arc tool again to unequip it // 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: 51 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: 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: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

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

View File

@ -1,19 +1,17 @@
import type { SaveSettingsPayload } from '@src/lib/settings/settingsTypes' import { Settings } from '@rust/kcl-lib/bindings/Settings'
import { Themes } from '@src/lib/theme' import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import type { DeepPartial } from '@src/lib/types' import { Themes } from 'lib/theme'
import { onboardingPaths } from '@src/routes/Onboarding/paths' import { DeepPartial } from 'lib/types'
import { onboardingPaths } from 'routes/Onboarding/paths'
import type { Settings } from '@rust/kcl-lib/bindings/Settings'
export const IS_PLAYWRIGHT_KEY = 'playwright' export const IS_PLAYWRIGHT_KEY = 'playwright'
export const TEST_SETTINGS_KEY = '/settings.toml' export const TEST_SETTINGS_KEY = '/settings.toml'
export const TEST_SETTINGS: DeepPartial<Settings> = { export const TEST_SETTINGS: DeepPartial<Settings> = {
app: { app: {
appearance: {
theme: Themes.Dark, theme: Themes.Dark,
},
onboarding_status: 'dismissed', onboarding_status: 'dismissed',
project_directory: '',
show_debug_panel: true, show_debug_panel: true,
}, },
modeling: { modeling: {
@ -23,8 +21,7 @@ export const TEST_SETTINGS: DeepPartial<Settings> = {
camera_projection: 'perspective', camera_projection: 'perspective',
}, },
project: { project: {
default_project_name: 'untitled', default_project_name: 'project-$nnn',
directory: '',
}, },
text_editor: { text_editor: {
text_wrapping: true, text_wrapping: true,
@ -57,7 +54,7 @@ export const TEST_SETTINGS_ONBOARDING_START: DeepPartial<Settings> = {
export const TEST_SETTINGS_DEFAULT_THEME: DeepPartial<Settings> = { export const TEST_SETTINGS_DEFAULT_THEME: DeepPartial<Settings> = {
...TEST_SETTINGS, ...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, appearance: { theme: Themes.System } }, app: { ...TEST_SETTINGS.app, theme: Themes.System },
} }
export const TEST_SETTINGS_CORRUPTED = { export const TEST_SETTINGS_CORRUPTED = {
@ -80,8 +77,7 @@ export const TEST_SETTINGS_CORRUPTED = {
}, },
} satisfies Partial<SaveSettingsPayload> } satisfies Partial<SaveSettingsPayload>
export const TEST_CODE_GIZMO = `@settings(defaultLengthUnit = in) export const TEST_CODE_GIZMO = `part001 = startSketchOn(XZ)
part001 = startSketchOn(XZ)
|> startProfileAt([20, 0], %) |> startProfileAt([20, 0], %)
|> line(end = [7.13, 4 + 0]) |> line(end = [7.13, 4 + 0])
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %) |> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)

View File

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

View File

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

View File

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

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