Compare commits
204 Commits
pierremtb/
...
nightly-v2
Author | SHA1 | Date | |
---|---|---|---|
10789d9c3c | |||
67cc4f5835 | |||
2692f2b73a | |||
965cb18059 | |||
a022b8ef6c | |||
4d24bf7c94 | |||
9a537da183 | |||
df81b76b8b | |||
ac3f7ab712 | |||
dac91d3b79 | |||
0698432abf | |||
0592d3b5da | |||
31e4d60045 | |||
c0817b00e4 | |||
4ea1d16fb6 | |||
d049bf33e8 | |||
7b11047d07 | |||
412e9568f2 | |||
9be208e5e1 | |||
842ef5ede9 | |||
3f855d7bad | |||
0a1a6e50cf | |||
d4e955289c | |||
c147a219f4 | |||
38513a1e25 | |||
c0c5c790ca | |||
8b60f75220 | |||
f91ad4331f | |||
59103a2118 | |||
9737c2550a | |||
bf9d01a8dd | |||
702e322f90 | |||
e82830754d | |||
7806377a5a | |||
859afa2fd8 | |||
0a5f3093fc | |||
b65f7939f6 | |||
c35dea5e07 | |||
fc66d4745f | |||
b313d26c2a | |||
00b94ead62 | |||
0531ea1ce9 | |||
5f9a4887c1 | |||
da7dfa16d8 | |||
363ae10658 | |||
ac4a6c84cf | |||
c6fad2e2dc | |||
013cb10961 | |||
6261083cb1 | |||
2b0ba37ed0 | |||
96174f3cf6 | |||
aed62ff912 | |||
9334d64608 | |||
4fa7d2d8c8 | |||
3e615dfdbc | |||
c9860af29f | |||
23a42f0195 | |||
a77fa639f3 | |||
0a5ad7c95b | |||
4a654523d2 | |||
73a7e2bfd6 | |||
eb0850fea9 | |||
029f76f273 | |||
28b5f7080c | |||
5b1dcfecd6 | |||
f89d191425 | |||
2f4e4b62a8 | |||
5ebd5c8dbb | |||
a9ceaf2678 | |||
c8afd3399b | |||
5dda4828c6 | |||
72acab752c | |||
81df38ad1c | |||
0576a2bef1 | |||
4b2f6b4647 | |||
69edaa4183 | |||
2eb7c382bf | |||
38913ecb98 | |||
debd06129f | |||
d38bd342a0 | |||
f026f10335 | |||
895d7ebc6d | |||
65edf17a44 | |||
0c2a0a8c07 | |||
97cef4d16c | |||
9358278f7b | |||
a174e084d4 | |||
df7246897a | |||
0c9f64dd7c | |||
d2b9d3a058 | |||
7e54f08778 | |||
d9c2dd376e | |||
275a2150e7 | |||
8b8feb8d68 | |||
e21ef3f122 | |||
66834931aa | |||
06c1bcaf2e | |||
fc7df7ecbe | |||
67cb7b33bb | |||
ea74b94fac | |||
529833c63f | |||
92da86391a | |||
e7cb390db4 | |||
8a66bbbdbd | |||
3c53babb50 | |||
474acb1c68 | |||
1c941112d7 | |||
6f1d718097 | |||
36957237c0 | |||
da9cae98aa | |||
9ae025dc56 | |||
579ab23d78 | |||
4bef33e745 | |||
ac7bd28c5a | |||
d478d81156 | |||
3d27f0191b | |||
30c2acd18a | |||
a83b4b2145 | |||
70b8541038 | |||
bb51646738 | |||
c02e31a530 | |||
1d06cc7845 | |||
e0c07eecfe | |||
c5d42500fa | |||
e6e47f77f0 | |||
662c2485ac | |||
9f891deebb | |||
d08a07a1f8 | |||
872b196a86 | |||
d535a2862d | |||
63a3bc7bc6 | |||
02055a8b31 | |||
93891422f7 | |||
7193b4110a | |||
76e7d80a55 | |||
b816df21d2 | |||
3630696848 | |||
f165d19fda | |||
3dd98ae1d5 | |||
a46e0a0fe7 | |||
8f9dc06228 | |||
fa22c14723 | |||
1d39983b08 | |||
da301ba862 | |||
efe8089b08 | |||
49de3b0ac9 | |||
2b2ed470c1 | |||
96652a0c48 | |||
04e586d07b | |||
fe5f574a77 | |||
e787495ad0 | |||
8bb9be7a5e | |||
00892464e8 | |||
05ed2a3367 | |||
10cc5bce59 | |||
a32f150fc1 | |||
ac60082e67 | |||
d44dc1b21a | |||
813962ea4c | |||
738443a6ab | |||
4b6bbbe2c5 | |||
6ff8addc8b | |||
da05c38b9e | |||
191b9b71fd | |||
05163fdded | |||
7ed26e21c6 | |||
c668d40efc | |||
f38c6b90b7 | |||
7bc8bae0ec | |||
3804aca27e | |||
b127680f2f | |||
b7de8e60cf | |||
058fccb5e1 | |||
00e97257ae | |||
aeb656d176 | |||
ac49ebd6e0 | |||
b40f03ad25 | |||
a8ad86e645 | |||
87f50cd5e9 | |||
0400e6228e | |||
26f150fd6c | |||
3049f405f5 | |||
53d40301dc | |||
671c01e36f | |||
e80151979b | |||
668e2afb99 | |||
548c664db0 | |||
d3a3f4410c | |||
22eb343171 | |||
f2cfa4d5cf | |||
3f1f40eeba | |||
ff2d161606 | |||
210c78029d | |||
e27840219b | |||
c943a3f192 | |||
6aa588f09f | |||
59a6333aad | |||
403f1507ae | |||
eac7b83504 | |||
667500d1b9 | |||
b15aac9f48 | |||
54153aa646 | |||
943cf21d34 | |||
5a6728c45a |
@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
|
||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall,ser
|
||||
skip: **/target,node_modules,build,dist,./out,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./packages/codemirror-lang-kcl/test/all.test.ts,tsconfig.tsbuildinfo
|
||||
|
28
.eslintrc
@ -5,16 +5,32 @@
|
||||
},
|
||||
"plugins": [
|
||||
"css-modules",
|
||||
"jest",
|
||||
"jsx-a11y",
|
||||
"react",
|
||||
"react-hooks",
|
||||
"suggest-no-throw",
|
||||
"testing-library",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest",
|
||||
"plugin:css-modules/recommended"
|
||||
"plugin:css-modules/recommended",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "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."
|
||||
}
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
@ -25,6 +41,9 @@
|
||||
"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",
|
||||
@ -33,6 +52,9 @@
|
||||
},
|
||||
{
|
||||
"files": ["src/**/*.test.ts"],
|
||||
"extends": [
|
||||
"plugin:testing-library/react"
|
||||
],
|
||||
"rules": {
|
||||
"suggest-no-throw/suggest-no-throw": "off",
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
# bash strict mode
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
if [[ "$3" == ubuntu-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:ubuntu -- --shard=$1/$2 || true
|
||||
elif [[ "$3" == windows-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:windows -- --shard=$1/$2 || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
exit 1
|
||||
fi
|
||||
# # send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
retry=1
|
||||
max_retrys=4
|
||||
|
||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||
while [[ $retry -le $max_retrys ]]; do
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
if [[ "$3" == ubuntu-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:ubuntu -- --last-failed || true
|
||||
elif [[ "$3" == windows-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:windows -- --last-failed || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
exit 1
|
||||
fi
|
||||
# send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
retry=$((retry + 1))
|
||||
else
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
# if it still fails after 3 retrys, then fail the job
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
exit 0
|
22
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -1,15 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# bash strict mode
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
if [[ "$1" == ubuntu-latest* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true
|
||||
elif [[ "$1" == windows-latest* ]]; then
|
||||
yarn test:playwright:electron:windows || true
|
||||
elif [[ "$1" == macos-14* ]]; then
|
||||
yarn test:playwright:electron:macos || true
|
||||
if [[ "$3" == *ubuntu* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --shard=$1/$2 || true
|
||||
elif [[ "$3" == *windows* ]]; then
|
||||
yarn test:playwright:electron:windows -- --shard=$1/$2 || true
|
||||
elif [[ "$3" == *macos* ]]; then
|
||||
yarn test:playwright:electron:macos -- --shard=$1/$2 || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
exit 1
|
||||
@ -19,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
fi
|
||||
|
||||
retry=1
|
||||
max_retrys=4
|
||||
max_retrys=5
|
||||
|
||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||
while [[ $retry -le $max_retrys ]]; do
|
||||
@ -28,11 +30,11 @@ while [[ $retry -le $max_retrys ]]; do
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
if [[ "$1" == ubuntu-latest* ]]; then
|
||||
if [[ "$3" == *ubuntu* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true
|
||||
elif [[ "$1" == windows-latest* ]]; then
|
||||
elif [[ "$3" == *windows* ]]; then
|
||||
yarn test:playwright:electron:windows -- --last-failed || true
|
||||
elif [[ "$1" == macos-14* ]]; then
|
||||
elif [[ "$3" == *macos* ]]; then
|
||||
yarn test:playwright:electron:macos -- --last-failed || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
|
55
.github/dependabot.yml
vendored
@ -5,24 +5,37 @@
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
reviewers:
|
||||
- franknoirot
|
||||
- irev-dev
|
||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src/wasm-lib/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'npm' # See documentation for possible values
|
||||
directories:
|
||||
- '/'
|
||||
- '/packages/codemirror-lang-kcl/'
|
||||
- '/packages/codemirror-lsp-client/'
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
reviewers:
|
||||
- franknoirot
|
||||
- irev-dev
|
||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src/wasm-lib/' # Location of package manifests
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
groups:
|
||||
serde-dependencies:
|
||||
patterns:
|
||||
- "serde*"
|
||||
wasm-bindgen-deps:
|
||||
patterns:
|
||||
- "wasm-bindgen*"
|
||||
|
2
.github/workflows/build-and-store-wasm.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
|
||||
# Upload the WASM bundle as an artifact
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wasm-bundle
|
||||
path: src/wasm-lib/pkg
|
||||
|
27
.github/workflows/build-apps.yml
vendored
@ -126,7 +126,13 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
||||
|
||||
- run: yarn install
|
||||
- name: yarn install
|
||||
# Windows is picky sometimes and fails on fetch. Step takes about ~30s
|
||||
uses: nick-fields/retry@v3.0.0
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: yarn install
|
||||
|
||||
- run: yarn tronb:vite
|
||||
|
||||
@ -165,7 +171,6 @@ jobs:
|
||||
- name: Build the app (release)
|
||||
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
|
||||
env:
|
||||
PUBLISH_FOR_PULL_REQUEST: true
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
@ -173,9 +178,14 @@ jobs:
|
||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||
run: yarn electron-builder --config --publish always
|
||||
DEBUG: "electron-notarize*"
|
||||
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
|
||||
uses: nick-fields/retry@v3.0.0
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: yarn electron-builder --config --publish always
|
||||
|
||||
- name: List artifacts in out/
|
||||
run: ls -R out
|
||||
@ -229,9 +239,14 @@ jobs:
|
||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||
run: yarn electron-builder --config --publish always
|
||||
DEBUG: "electron-notarize*"
|
||||
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
|
||||
uses: nick-fields/retry@v3.0.0
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
command: yarn electron-builder --config --publish always
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ env.IS_RELEASE == 'true' }}
|
||||
|
44
.github/workflows/cargo-bench.yml
vendored
@ -1,44 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-bench.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-bench.yml
|
||||
workflow_dispatch:
|
||||
permissions: read-all
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
name: cargo bench
|
||||
jobs:
|
||||
cargo-bench:
|
||||
name: Benchmark with iai
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cargo install cargo-criterion
|
||||
sudo apt update
|
||||
sudo apt install -y valgrind
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.6.1
|
||||
- name: Benchmark kcl library
|
||||
shell: bash
|
||||
run: |-
|
||||
cd src/wasm-lib/kcl; cargo bench --all-features -- iai
|
||||
env:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||
|
22
.github/workflows/cargo-test.yml
vendored
@ -2,28 +2,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'src/wasm-lib/**.rs'
|
||||
- 'src/wasm-lib/**.hbs'
|
||||
- 'src/wasm-lib/**.gen'
|
||||
- 'src/wasm-lib/**.snap'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- 'src/wasm-lib/**.kcl'
|
||||
- .github/workflows/cargo-test.yml
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/wasm-lib/**.rs'
|
||||
- 'src/wasm-lib/**.hbs'
|
||||
- 'src/wasm-lib/**.gen'
|
||||
- 'src/wasm-lib/**.snap'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- 'src/wasm-lib/**.kcl'
|
||||
- .github/workflows/cargo-test.yml
|
||||
workflow_dispatch:
|
||||
permissions: read-all
|
||||
concurrency:
|
||||
@ -71,7 +51,7 @@ jobs:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||
RUST_MIN_STACK: 10485760000
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{secrets.CODECOV_TOKEN}}
|
||||
fail_ci_if_error: true
|
||||
|
32
.github/workflows/codemirror-lang-kcl.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: CodeMirror Lang KCL
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
yarn-unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
working-directory: packages/codemirror-lang-kcl
|
||||
|
||||
- run: yarn tsc
|
||||
working-directory: packages/codemirror-lang-kcl
|
||||
|
||||
- name: run unit tests
|
||||
run: yarn test
|
||||
working-directory: packages/codemirror-lang-kcl
|
166
.github/workflows/e2e-tests.yml
vendored
@ -33,20 +33,19 @@ jobs:
|
||||
rust:
|
||||
- 'src/wasm-lib/**'
|
||||
|
||||
browser:
|
||||
timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }}
|
||||
name: playwright:browser:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
||||
electron:
|
||||
timeout-minutes: 60
|
||||
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores]
|
||||
# TODO: enable self-hosted-windows-8-cores once available
|
||||
os: [namespace-profile-ubuntu-8-cores, namespace-profile-macos-8-cores, windows-16-cores]
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: check-rust-changes
|
||||
steps:
|
||||
- name: Tune GitHub-hosted runner network
|
||||
uses: smorimoto/tune-github-hosted-runner-network@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
@ -101,7 +100,8 @@ jobs:
|
||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Install vector
|
||||
shell: bash
|
||||
if: ${{ !startsWith(matrix.os, 'windows') }}
|
||||
# TODO: figure out what to do with this, it's failing
|
||||
if: false
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
chmod +x /tmp/vector.sh
|
||||
@ -123,13 +123,16 @@ jobs:
|
||||
if: steps.download-wasm.outcome == 'failure'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: build web
|
||||
run: yarn build:local
|
||||
- name: build electron
|
||||
shell: bash
|
||||
run: yarn tron:package
|
||||
- name: Run ubuntu/chrome snapshots
|
||||
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
||||
shell: bash
|
||||
# TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest,
|
||||
# but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes.
|
||||
run: |
|
||||
yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: development
|
||||
@ -150,6 +153,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: rm -r test-results
|
||||
- name: check for changes
|
||||
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
||||
shell: bash
|
||||
id: git-check
|
||||
run: |
|
||||
@ -186,12 +190,12 @@ jobs:
|
||||
with:
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run playwright/chrome flow (with retries)
|
||||
- name: Run playwright/electron flow (with retries)
|
||||
id: retry
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
.github/ci-cd-scripts/playwright-browser-chrome.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
|
||||
.github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
|
||||
env:
|
||||
CI: true
|
||||
FAIL_ON_CONSOLE_ERRORS: true
|
||||
@ -199,11 +203,6 @@ jobs:
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- name: send to axiom
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
@ -221,136 +220,3 @@ jobs:
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
|
||||
electron:
|
||||
name: playwright:electron:${{matrix.os}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large]
|
||||
timeout-minutes: 60
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: check-rust-changes
|
||||
steps:
|
||||
- name: Tune GitHub-hosted runner network
|
||||
uses: smorimoto/tune-github-hosted-runner-network@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- uses: KittyCAD/action-install-cli@main
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: yarn
|
||||
- name: Cache Playwright Browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright/
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
|
||||
- name: Install Playwright Browsers
|
||||
shell: bash
|
||||
run: yarn playwright install chromium --with-deps
|
||||
- name: Download Wasm Cache
|
||||
id: download-wasm
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
continue-on-error: true
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
name: wasm-bundle
|
||||
workflow: build-and-store-wasm.yml
|
||||
branch: main
|
||||
path: src/wasm-lib/pkg
|
||||
- name: copy wasm blob
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||
shell: bash
|
||||
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
continue-on-error: true
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cache Wasm (because rust diff)
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: OR Cache Wasm (because wasm cache failed)
|
||||
if: steps.download-wasm.outcome == 'failure'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: install good sed
|
||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
||||
shell: bash
|
||||
run: |
|
||||
brew install gnu-sed
|
||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Install vector
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
chmod +x /tmp/vector.sh
|
||||
/tmp/vector.sh -y -no-modify-path
|
||||
mkdir -p /tmp/vector
|
||||
cp .github/workflows/vector.toml /tmp/vector.toml
|
||||
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
|
||||
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
|
||||
cat /tmp/vector.toml
|
||||
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
|
||||
- name: Build Wasm (because rust diff)
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: OR Build Wasm (because wasm cache failed)
|
||||
if: steps.download-wasm.outcome == 'failure'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: build electron
|
||||
shell: bash
|
||||
run: yarn tron:package
|
||||
- uses: actions/download-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run electron tests (with retries)
|
||||
id: retry
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
.github/ci-cd-scripts/playwright-electron.sh ${{ matrix.os }}
|
||||
env:
|
||||
CI: true
|
||||
FAIL_ON_CONSOLE_ERRORS: true
|
||||
NODE_ENV: development
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
IS_UBUNTU: ${{ startsWith(matrix.os, 'ubuntu') && 'true' || 'false' }}
|
||||
#DEBUG: 'pw:browser*'
|
||||
- name: send to axiom
|
||||
if: ${{ !cancelled() && (success() || failure()) && !startsWith(matrix.os, 'windows') }}
|
||||
shell: bash
|
||||
run: |
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
57
README.md
@ -337,13 +337,47 @@ For individual testing:
|
||||
yarn test abstractSyntaxTree -t "unexpected closed curly brace" --silent=false
|
||||
```
|
||||
|
||||
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
||||
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro) tests, in interactive mode by default.
|
||||
|
||||
### Rust tests
|
||||
|
||||
```bash
|
||||
**Dependencies**
|
||||
|
||||
- `KITTYCAD_API_TOKEN`
|
||||
- `cargo-nextest`
|
||||
- `just`
|
||||
|
||||
#### Setting KITTYCAD_API_TOKEN
|
||||
Use the production zoo.dev token, set this environment variable before running the tests
|
||||
|
||||
#### Installing cargonextest
|
||||
|
||||
```
|
||||
cd src/wasm-lib
|
||||
KITTYCAD_API_TOKEN=XXX cargo test -- --test-threads=1
|
||||
cargo search cargo-nextest
|
||||
cargo install cargo-nextest
|
||||
```
|
||||
|
||||
#### just
|
||||
install [`just`](https://github.com/casey/just?tab=readme-ov-file#pre-built-binaries)
|
||||
|
||||
#### Running the tests
|
||||
|
||||
```bash
|
||||
# With just
|
||||
# Make sure KITTYCAD_API_TOKEN=<prod zoo.dev token> is set
|
||||
# Make sure you installed cargo-nextest
|
||||
# Make sure you installed just
|
||||
cd src/wasm-lib
|
||||
just test
|
||||
```
|
||||
|
||||
```bash
|
||||
# Without just
|
||||
# Make sure KITTYCAD_API_TOKEN=<prod zoo.dev token> is set
|
||||
# Make sure you installed cargo-nextest
|
||||
cd src/wasm-lib
|
||||
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1
|
||||
```
|
||||
|
||||
Where `XXX` is an API token from the production engine (NOT the dev environment).
|
||||
@ -388,23 +422,6 @@ yarn test:unit:local
|
||||
|
||||
#### E2E Tests
|
||||
|
||||
**Playwright Browser**
|
||||
|
||||
These E2E tests run in a browser (without electron).
|
||||
There are tests that are skipped if they are ran in a windows OS or Linux OS. We can use playwright tags to implement test skipping.
|
||||
|
||||
Breaking down the command `yarn test:playwright:browser:chrome:windows`
|
||||
- The application is `playwright`
|
||||
- The runtime is a `browser`
|
||||
- The specific `browser` is `chrome`
|
||||
- The test should run in a `windows` environment. It will skip tests that are broken or flaky in the windows OS.
|
||||
|
||||
```
|
||||
yarn test:playwright:browser:chrome
|
||||
yarn test:playwright:browser:chrome:windows
|
||||
yarn test:playwright:browser:chrome:ubuntu
|
||||
```
|
||||
|
||||
**Playwright Electron**
|
||||
|
||||
These E2E tests run in electron. There are tests that are skipped if they are ran in a windows, linux, or macos environment. We can use playwright tags to implement test skipping.
|
||||
|
@ -22,3 +22,5 @@ once fixed in engine will just start working here with no language changes.
|
||||
|
||||
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||
chamfer cases work currently.
|
||||
|
||||
- **Appearance**: Changing the appearance on a loft does not work.
|
||||
|
239
docs/kcl/appearance.md
Normal file
49
docs/kcl/atan2.md
Normal file
42
docs/kcl/circleThreePoint.md
Normal file
43
docs/kcl/helixRevolutions.md
Normal file
@ -19,6 +19,7 @@ layout: manual
|
||||
* [`angledLineThatIntersects`](kcl/angledLineThatIntersects)
|
||||
* [`angledLineToX`](kcl/angledLineToX)
|
||||
* [`angledLineToY`](kcl/angledLineToY)
|
||||
* [`appearance`](kcl/appearance)
|
||||
* [`arc`](kcl/arc)
|
||||
* [`arcTo`](kcl/arcTo)
|
||||
* [`asin`](kcl/asin)
|
||||
@ -29,10 +30,12 @@ layout: manual
|
||||
* [`assertLessThan`](kcl/assertLessThan)
|
||||
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
||||
* [`atan`](kcl/atan)
|
||||
* [`atan2`](kcl/atan2)
|
||||
* [`bezierCurve`](kcl/bezierCurve)
|
||||
* [`ceil`](kcl/ceil)
|
||||
* [`chamfer`](kcl/chamfer)
|
||||
* [`circle`](kcl/circle)
|
||||
* [`circleThreePoint`](kcl/circleThreePoint)
|
||||
* [`close`](kcl/close)
|
||||
* [`cm`](kcl/cm)
|
||||
* [`cos`](kcl/cos)
|
||||
@ -45,11 +48,11 @@ layout: manual
|
||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||
* [`helix`](kcl/helix)
|
||||
* [`helixRevolutions`](kcl/helixRevolutions)
|
||||
* [`hole`](kcl/hole)
|
||||
* [`hollow`](kcl/hollow)
|
||||
* [`import`](kcl/import)
|
||||
* [`inch`](kcl/inch)
|
||||
* [`int`](kcl/int)
|
||||
* [`lastSegX`](kcl/lastSegX)
|
||||
* [`lastSegY`](kcl/lastSegY)
|
||||
* [`legAngX`](kcl/legAngX)
|
||||
@ -78,6 +81,7 @@ layout: manual
|
||||
* [`pi`](kcl/pi)
|
||||
* [`polar`](kcl/polar)
|
||||
* [`polygon`](kcl/polygon)
|
||||
* [`pop`](kcl/pop)
|
||||
* [`pow`](kcl/pow)
|
||||
* [`profileStart`](kcl/profileStart)
|
||||
* [`profileStartX`](kcl/profileStartX)
|
||||
@ -99,8 +103,8 @@ layout: manual
|
||||
* [`sin`](kcl/sin)
|
||||
* [`sqrt`](kcl/sqrt)
|
||||
* [`startProfileAt`](kcl/startProfileAt)
|
||||
* [`startSketchAt`](kcl/startSketchAt)
|
||||
* [`startSketchOn`](kcl/startSketchOn)
|
||||
* [`sweep`](kcl/sweep)
|
||||
* [`tan`](kcl/tan)
|
||||
* [`tangentToEnd`](kcl/tangentToEnd)
|
||||
* [`tangentialArc`](kcl/tangentialArc)
|
||||
|
@ -4,6 +4,8 @@ excerpt: "Convert a number to an integer."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Convert a number to an integer.
|
||||
|
||||
DEPRECATED use floor(), ceil(), or round().
|
||||
|
@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
|
||||
```js
|
||||
r = 10 // radius
|
||||
// Call `map`, using an anonymous function instead of a named one.
|
||||
circles = map([1..3], (id) {
|
||||
circles = map([1..3], fn(id) {
|
||||
return startSketchOn("XY")
|
||||
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
||||
})
|
||||
|
@ -35,7 +35,7 @@ The transform function returns a transform object. All properties of the object
|
||||
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
|
||||
|
||||
```js
|
||||
patternTransform(total_instances: u32, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid]
|
||||
patternTransform(total_instances: integer, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid]
|
||||
```
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ patternTransform(total_instances: u32, transform_function: FunctionParam, solid_
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `total_instances` | `u32` | | Yes |
|
||||
| `total_instances` | `integer` | | Yes |
|
||||
| `transform_function` | `FunctionParam` | | Yes |
|
||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
|
||||
|
||||
@ -95,7 +95,8 @@ fn cube(length, center) {
|
||||
p2 = [l + x, l + y]
|
||||
p3 = [l + x, -l + y]
|
||||
|
||||
return startSketchAt(p0)
|
||||
return startSketchOn('XY')
|
||||
|> startProfileAt(p0, %)
|
||||
|> lineTo(p1, %)
|
||||
|> lineTo(p2, %)
|
||||
|> lineTo(p3, %)
|
||||
@ -132,7 +133,8 @@ fn cube(length, center) {
|
||||
p2 = [l + x, l + y]
|
||||
p3 = [l + x, -l + y]
|
||||
|
||||
return startSketchAt(p0)
|
||||
return startSketchOn('XY')
|
||||
|> startProfileAt(p0, %)
|
||||
|> lineTo(p1, %)
|
||||
|> lineTo(p2, %)
|
||||
|> lineTo(p3, %)
|
||||
@ -195,7 +197,8 @@ fn transform(i) {
|
||||
{ rotation = { angle = 45 * i } }
|
||||
]
|
||||
}
|
||||
startSketchAt([0, 0])
|
||||
startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> polygon({
|
||||
radius = 10,
|
||||
numSides = 4,
|
||||
|
@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
|
||||
|
||||
|
||||
```js
|
||||
patternTransform2d(total_instances: u32, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch]
|
||||
patternTransform2d(total_instances: integer, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ patternTransform2d(total_instances: u32, transform_function: FunctionParam, soli
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `total_instances` | `u32` | | Yes |
|
||||
| `total_instances` | `integer` | | Yes |
|
||||
| `transform_function` | `FunctionParam` | | Yes |
|
||||
| `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||
|
||||
|
39
docs/kcl/pop.md
Normal file
@ -43,7 +43,7 @@ fn sum(arr) {
|
||||
|
||||
/* The above is basically like this pseudo-code:
|
||||
fn sum(arr):
|
||||
let sumSoFar = 0
|
||||
sumSoFar = 0
|
||||
for i in arr:
|
||||
sumSoFar = add(sumSoFar, i)
|
||||
return sumSoFar */
|
||||
@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
|
||||
// an anonymous `add` function as its parameter, instead of declaring a
|
||||
// named function outside.
|
||||
arr = [1, 2, 3]
|
||||
sum = reduce(arr, 0, (i, result_so_far) {
|
||||
sum = reduce(arr, 0, fn(i, result_so_far) {
|
||||
return i + result_so_far
|
||||
})
|
||||
|
||||
@ -79,12 +79,13 @@ fn decagon(radius) {
|
||||
stepAngle = 1 / 10 * tau()
|
||||
|
||||
// Start the decagon sketch at this point.
|
||||
startOfDecagonSketch = startSketchAt([cos(0) * radius, sin(0) * radius])
|
||||
startOfDecagonSketch = startSketchOn('XY')
|
||||
|> startProfileAt([cos(0) * radius, sin(0) * radius], %)
|
||||
|
||||
// Use a `reduce` to draw the remaining decagon sides.
|
||||
// For each number in the array 1..10, run the given function,
|
||||
// Use a `reduce` to draw the remaining decagon sides.
|
||||
// For each number in the array 1..10, run the given function,
|
||||
// which takes a partially-sketched decagon and adds one more edge to it.
|
||||
fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {
|
||||
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
|
||||
// Draw one edge of the decagon.
|
||||
x = cos(stepAngle * i) * radius
|
||||
y = sin(stepAngle * i) * radius
|
||||
@ -96,14 +97,15 @@ fn decagon(radius) {
|
||||
|
||||
/* The `decagon` above is basically like this pseudo-code:
|
||||
fn decagon(radius):
|
||||
let stepAngle = (1/10) * tau()
|
||||
let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
||||
stepAngle = (1/10) * tau()
|
||||
plane = startSketchOn('XY')
|
||||
startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)
|
||||
|
||||
// Here's the reduce part.
|
||||
let partialDecagon = startOfDecagonSketch
|
||||
partialDecagon = startOfDecagonSketch
|
||||
for i in [1..10]:
|
||||
let x = cos(stepAngle * i) * radius
|
||||
let y = sin(stepAngle * i) * radius
|
||||
x = cos(stepAngle * i) * radius
|
||||
y = sin(stepAngle * i) * radius
|
||||
partialDecagon = lineTo([x, y], partialDecagon)
|
||||
fullDecagon = partialDecagon // it's now full
|
||||
return fullDecagon */
|
||||
|
@ -28,7 +28,8 @@ segEnd(tag: TagIdentifier) -> [number]
|
||||
|
||||
```js
|
||||
w = 15
|
||||
cube = startSketchAt([0, 0])
|
||||
cube = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([w, 0], %, $line1)
|
||||
|> line([0, w], %, $line2)
|
||||
|> line([-w, 0], %, $line3)
|
||||
@ -37,7 +38,8 @@ cube = startSketchAt([0, 0])
|
||||
|> extrude(5, %)
|
||||
|
||||
fn cylinder(radius, tag) {
|
||||
return startSketchAt([0, 0])
|
||||
return startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> circle({
|
||||
radius = radius,
|
||||
center = segEnd(tag)
|
||||
|
@ -28,7 +28,8 @@ segStart(tag: TagIdentifier) -> [number]
|
||||
|
||||
```js
|
||||
w = 15
|
||||
cube = startSketchAt([0, 0])
|
||||
cube = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([w, 0], %, $line1)
|
||||
|> line([0, w], %, $line2)
|
||||
|> line([-w, 0], %, $line3)
|
||||
@ -37,7 +38,8 @@ cube = startSketchAt([0, 0])
|
||||
|> extrude(5, %)
|
||||
|
||||
fn cylinder(radius, tag) {
|
||||
return startSketchAt([0, 0])
|
||||
return startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> circle({
|
||||
radius = radius,
|
||||
center = segStart(tag)
|
||||
|
@ -4,6 +4,8 @@ excerpt: "Start a new 2-dimensional sketch at a given point on the 'XY' plane."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Start a new 2-dimensional sketch at a given point on the 'XY' plane.
|
||||
|
||||
|
||||
|
41183
docs/kcl/std.json
77
docs/kcl/sweep.md
Normal file
@ -13,13 +13,18 @@ Data to draw an angled line.
|
||||
|
||||
An angle and length with explicitly named parameters
|
||||
|
||||
[`PolarCoordsData`](/docs/kcl/types/PolarCoordsData)
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `angle` |`number`| The angle of the line (in degrees). | No |
|
||||
| `length` |`number`| The length of the line. | No |
|
||||
|
||||
|
||||
----
|
||||
|
23
docs/kcl/types/AppearanceData.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "AppearanceData"
|
||||
excerpt: "Data for appearance."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Data for appearance.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `color` |`string`| Color of the new material, a hex string like "#ff0000". | No |
|
||||
| `metalness` |`number` (**maximum:** 100.0)| Metalness of the new material, a percentage like 95.7. | No |
|
||||
| `roughness` |`number` (**maximum:** 100.0)| Roughness of the new material, a percentage like 95.7. | No |
|
||||
|
||||
|
@ -1,19 +1,19 @@
|
||||
---
|
||||
title: "AxisOrEdgeReference"
|
||||
excerpt: "Axis or tagged edge."
|
||||
title: "Axis2dOrEdgeReference"
|
||||
excerpt: "A 2D axis or tagged edge."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Axis or tagged edge.
|
||||
A 2D axis or tagged edge.
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts any of the following:**
|
||||
|
||||
Axis and origin.
|
||||
2D axis and origin.
|
||||
|
||||
[`AxisAndOrigin`](/docs/kcl/types/AxisAndOrigin)
|
||||
[`AxisAndOrigin2d`](/docs/kcl/types/AxisAndOrigin2d)
|
||||
|
||||
|
||||
|
42
docs/kcl/types/Axis3dOrEdgeReference.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
title: "Axis3dOrEdgeReference"
|
||||
excerpt: "A 3D axis or tagged edge."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A 3D axis or tagged edge.
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts any of the following:**
|
||||
|
||||
3D axis and origin.
|
||||
|
||||
[`AxisAndOrigin3d`](/docs/kcl/types/AxisAndOrigin3d)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Tagged edge.
|
||||
|
||||
[`EdgeReference`](/docs/kcl/types/EdgeReference)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "AxisAndOrigin"
|
||||
excerpt: "Axis and origin."
|
||||
title: "AxisAndOrigin2d"
|
||||
excerpt: "A 2D axis and origin."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Axis and origin.
|
||||
A 2D axis and origin.
|
||||
|
||||
|
||||
|
105
docs/kcl/types/AxisAndOrigin3d.md
Normal file
@ -0,0 +1,105 @@
|
||||
---
|
||||
title: "AxisAndOrigin3d"
|
||||
excerpt: "A 3D axis and origin."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A 3D axis and origin.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
X-axis.
|
||||
|
||||
**enum:** `X`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Y-axis.
|
||||
|
||||
**enum:** `Y`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Z-axis.
|
||||
|
||||
**enum:** `Z`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Flip the X-axis.
|
||||
|
||||
**enum:** `-X`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Flip the Y-axis.
|
||||
|
||||
**enum:** `-Y`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Flip the Z-axis.
|
||||
|
||||
**enum:** `-Z`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `custom` |`object`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
23
docs/kcl/types/CircleThreePointData.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "CircleThreePointData"
|
||||
excerpt: "Data for drawing a 3-point circle"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Data for drawing a 3-point circle
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `p1` |`[number, number]`| Point one for circle derivation. | No |
|
||||
| `p2` |`[number, number]`| Point two for circle derivation. | No |
|
||||
| `p3` |`[number, number]`| Point three for circle derivation. | No |
|
||||
|
||||
|
28
docs/kcl/types/Face.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: "Face"
|
||||
excerpt: "A face."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A face.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `id` |`string`| The id of the face. | No |
|
||||
| `value` |`string`| The tag of the face. | No |
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A face. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
26
docs/kcl/types/Helix.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: "Helix"
|
||||
excerpt: "A helix."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A helix.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `value` |`string`| The id of the helix. | No |
|
||||
| `revolutions` |`number`| Number of revolutions. | No |
|
||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "HelixData"
|
||||
excerpt: "Data for helices."
|
||||
excerpt: "Data for a helix."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Data for helices.
|
||||
Data for a helix.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
@ -19,6 +19,8 @@ Data for helices.
|
||||
| `revolutions` |`number`| Number of revolutions. | No |
|
||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||
| `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No |
|
||||
| `length` |`number`| Length of the helix. This is not necessary if the helix is created around an edge. If not given the length of the edge is used. | No |
|
||||
| `radius` |`number`| Radius of the helix. | No |
|
||||
| `axis` |[`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference)| Axis to use as mirror. | No |
|
||||
|
||||
|
||||
|
24
docs/kcl/types/HelixRevolutionsData.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "HelixRevolutionsData"
|
||||
excerpt: "Data for helix revolutions."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Data for helix revolutions.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `revolutions` |`number`| Number of revolutions. | No |
|
||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||
| `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No |
|
||||
|
||||
|
26
docs/kcl/types/HelixValue.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: "HelixValue"
|
||||
excerpt: "A helix."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A helix.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `value` |`string`| The id of the helix. | No |
|
||||
| `revolutions` |`number`| Number of revolutions. | No |
|
||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
@ -12,5 +12,10 @@ KCL value for an optional parameter which was not given an argument. (remember,
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
|
||||
|
||||
|
@ -168,7 +168,6 @@ Any KCL value.
|
||||
|
||||
|
||||
----
|
||||
A plane.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
@ -181,17 +180,10 @@ A plane.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
|
||||
| `id` |`string`| The id of the plane. | No |
|
||||
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
|
||||
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
| `value` |[`Plane`](/docs/kcl/types/Plane)| Any KCL value. | No |
|
||||
|
||||
|
||||
----
|
||||
A face.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
@ -203,14 +195,8 @@ A face.
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Face`| | No |
|
||||
| `id` |`string`| The id of the face. | No |
|
||||
| `value` |`string`| The tag of the face. | No |
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
| `type` |enum: [`Face`](/docs/kcl/types/Face)| | No |
|
||||
| `value` |[`Face`](/docs/kcl/types/Face)| Any KCL value. | No |
|
||||
|
||||
|
||||
----
|
||||
@ -246,7 +232,6 @@ A face.
|
||||
|
||||
|
||||
----
|
||||
An solid is a collection of extrude surfaces.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
@ -259,14 +244,7 @@ An solid is a collection of extrude surfaces.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
|
||||
| `id` |`string`| The id of the solid. | No |
|
||||
| `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No |
|
||||
| `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No |
|
||||
| `height` |`number`| The height of the solid. | No |
|
||||
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
||||
| `endCapId` |`string`| The id of the extrusion end cap | No |
|
||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||
| `value` |[`Solid`](/docs/kcl/types/Solid)| Any KCL value. | No |
|
||||
|
||||
|
||||
----
|
||||
@ -285,6 +263,22 @@ An solid is a collection of extrude surfaces.
|
||||
| `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No |
|
||||
| `value` |[`Helix`](/docs/kcl/types/Helix)| Any KCL value. | No |
|
||||
|
||||
|
||||
----
|
||||
Data for an imported geometry.
|
||||
|
||||
@ -329,6 +323,23 @@ Data for an imported geometry.
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Module`| | No |
|
||||
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Any KCL value. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|
@ -16,6 +16,6 @@ Data for a mirror.
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis to use as mirror. | No |
|
||||
| `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis to use as mirror. | No |
|
||||
|
||||
|
||||
|
16
docs/kcl/types/ModuleId.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "ModuleId"
|
||||
excerpt: "Identifier of a source file. Uses a u32 to keep the size small."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Identifier of a source file. Uses a u32 to keep the size small.
|
||||
|
||||
**Type:** `integer` (`uint32`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@ A plane.
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A plane. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ Data for revolution surfaces.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `angle` |`number` (**maximum:** 360.0) (**minimum:** -360.0)| Angle to revolve (in degrees). Default is 360. | No |
|
||||
| `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis of revolution. | No |
|
||||
| `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis of revolution. | No |
|
||||
| `tolerance` |`number`| Tolerance for the revolve operation. | No |
|
||||
|
||||
|
||||
|
@ -21,6 +21,7 @@ A sketch is a collection of paths.
|
||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch is a collection of paths. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||
|
||||
|
||||
|
@ -30,6 +30,7 @@ A sketch is a collection of paths.
|
||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch or a group of sketches. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||
|
||||
|
||||
|
@ -31,6 +31,7 @@ A plane.
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
@ -54,6 +55,7 @@ A face.
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
|
@ -23,6 +23,7 @@ An solid is a collection of extrude surfaces.
|
||||
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
||||
| `endCapId` |`string`| The id of the extrusion end cap | No |
|
||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| An solid is a collection of extrude surfaces. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||
|
||||
|
||||
|
@ -32,6 +32,7 @@ An solid is a collection of extrude surfaces.
|
||||
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
||||
| `endCapId` |`string`| The id of the extrusion end cap | No |
|
||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A solid or a group of solids. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||
|
||||
|
||||
|
23
docs/kcl/types/SweepData.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "SweepData"
|
||||
excerpt: "Data for a sweep."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Data for a sweep.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `path` |[`SweepPath`](/docs/kcl/types/SweepPath)| The path to sweep along. | No |
|
||||
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
||||
| `tolerance` |`number`| Tolerance for the sweep operation. | No |
|
||||
|
||||
|
42
docs/kcl/types/SweepPath.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
title: "SweepPath"
|
||||
excerpt: "A path to sweep along."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A path to sweep along.
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts any of the following:**
|
||||
|
||||
A path to sweep along.
|
||||
|
||||
[`Sketch`](/docs/kcl/types/Sketch)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
A path to sweep along.
|
||||
|
||||
[`Helix`](/docs/kcl/types/Helix)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
107
docs/kcl/types/UnitLen.md
Normal file
@ -0,0 +1,107 @@
|
||||
---
|
||||
title: "UnitLen"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Mm`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Cm`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `M`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Inches`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Feet`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Yards`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,22 +1,11 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { setupElectron, tearDown } from './test-utils'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
import { test, expect } from './zoo-test'
|
||||
|
||||
test.describe('Electron app header tests', () => {
|
||||
test(
|
||||
'Open Command Palette button has correct shortcut',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
async ({ page }, testInfo) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
// No space before the shortcut since it checks textContent.
|
||||
let text
|
||||
@ -34,21 +23,14 @@ test.describe('Electron app header tests', () => {
|
||||
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandsButton).toBeVisible()
|
||||
await expect(commandsButton).toHaveText(text)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'User settings has correct shortcut',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
async ({ page }, testInfo) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
// Open the user sidebar menu.
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
@ -59,8 +41,6 @@ test.describe('Electron app header tests', () => {
|
||||
const userSettingsButton = page.getByTestId('user-settings')
|
||||
await expect(userSettingsButton).toBeVisible()
|
||||
await expect(userSettingsButton).toHaveText(text)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -1,29 +1,26 @@
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import {
|
||||
getUtils,
|
||||
TEST_COLORS,
|
||||
setup,
|
||||
tearDown,
|
||||
commonPoints,
|
||||
PERSIST_MODELING_CONTEXT,
|
||||
} from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||
|
||||
test.setTimeout(120000)
|
||||
|
||||
async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
async function doBasicSketch(
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
openPanes: string[]
|
||||
) {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.waitForTimeout(1000)
|
||||
await u.openDebugPanel()
|
||||
|
||||
// If we have the code pane open, we should see the code.
|
||||
@ -148,13 +145,11 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
}
|
||||
|
||||
test.describe('Basic sketch', () => {
|
||||
test('code pane open at start', { tag: ['@skipWin'] }, async ({ page }) => {
|
||||
// Skip on windows it is being weird.
|
||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||
await doBasicSketch(page, ['code'])
|
||||
test.fixme('code pane open at start', async ({ page, homePage }) => {
|
||||
await doBasicSketch(page, homePage, ['code'])
|
||||
})
|
||||
|
||||
test('code pane closed at start', async ({ page }) => {
|
||||
test('code pane closed at start', async ({ page, homePage }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async (persistModelingContext) => {
|
||||
localStorage.setItem(
|
||||
@ -162,6 +157,6 @@ test.describe('Basic sketch', () => {
|
||||
JSON.stringify({ openPanes: [] })
|
||||
)
|
||||
}, PERSIST_MODELING_CONTEXT)
|
||||
await doBasicSketch(page, [])
|
||||
await doBasicSketch(page, homePage, [])
|
||||
})
|
||||
})
|
||||
|
@ -1,27 +1,21 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||
import { getUtils } from './test-utils'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
const sketchOnPlaneAndBackSideTest = async (
|
||||
page: any,
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
plane: string,
|
||||
clickCoords: { x: number; y: number }
|
||||
) => {
|
||||
const u = await getUtils(page)
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const coord =
|
||||
@ -83,32 +77,39 @@ test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
await u.clearCommandLogs()
|
||||
await u.removeCurrentCode()
|
||||
}
|
||||
test('XY', async ({ page }) => {
|
||||
test('XY', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(
|
||||
page,
|
||||
homePage,
|
||||
'XY',
|
||||
{ x: 600, y: 388 } // red plane
|
||||
// { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
|
||||
)
|
||||
})
|
||||
|
||||
test('YZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, 'YZ', { x: 700, y: 250 }) // green plane
|
||||
test('YZ', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, 'YZ', { x: 700, y: 250 }) // green plane
|
||||
})
|
||||
|
||||
test('XZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 80 }) // blue plane
|
||||
test('XZ', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, '-XZ', { x: 700, y: 80 }) // blue plane
|
||||
})
|
||||
|
||||
test('-XY', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, '-XY', { x: 600, y: 118 }) // back of red plane
|
||||
test('-XY', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, '-XY', {
|
||||
x: 600,
|
||||
y: 118,
|
||||
}) // back of red plane
|
||||
})
|
||||
|
||||
test('-YZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, '-YZ', { x: 700, y: 219 }) // back of green plane
|
||||
test('-YZ', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, '-YZ', {
|
||||
x: 700,
|
||||
y: 219,
|
||||
}) // back of green plan
|
||||
})
|
||||
|
||||
test('-XZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 427 }) // back of blue plane
|
||||
test('-XZ', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, 'XZ', { x: 700, y: 427 }) // back of blue plane
|
||||
})
|
||||
})
|
||||
|
@ -1,28 +1,15 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect } from './zoo-test'
|
||||
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import { getUtils, executorInputPath } from './test-utils'
|
||||
import { join } from 'path'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Code pane and errors', () => {
|
||||
test('Typing KCL errors induces a badge on the code pane button', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -31,18 +18,18 @@ test.describe('Code pane and errors', () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`// Extruded Triangle
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([-5, 10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)`
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([-5, 10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -62,11 +49,11 @@ extrude001 = extrude(5, sketch001)`
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
})
|
||||
|
||||
test('Opening and closing the code pane will consistently show error diagnostics', async ({
|
||||
test.skip('Opening and closing the code pane will consistently show error diagnostics', async ({
|
||||
page,
|
||||
homePage,
|
||||
editor,
|
||||
}) => {
|
||||
await page.goto('http://localhost:3000')
|
||||
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
@ -74,8 +61,8 @@ extrude001 = extrude(5, sketch001)`
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 900 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.setBodyDimensions({ width: 1200, height: 900 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -91,8 +78,9 @@ extrude001 = extrude(5, sketch001)`
|
||||
await expect(codePaneButtonHolder).not.toContainText('notification')
|
||||
|
||||
// Delete a character to break the KCL
|
||||
await u.openKclCodePanel()
|
||||
await page.getByText('thickness, bracketLeg1Sketch)').click()
|
||||
await editor.openPane()
|
||||
await editor.scrollToText('thickness, bracketLeg1Sketch)')
|
||||
await page.getByText('extrude(thickness, bracketLeg1Sketch)').click()
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
// Ensure that a badge appears on the button
|
||||
@ -116,7 +104,10 @@ extrude001 = extrude(5, sketch001)`
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Open the code pane
|
||||
await u.openKclCodePanel()
|
||||
await editor.openPane()
|
||||
|
||||
// Go to our problematic code again (missing closing paren!)
|
||||
await editor.scrollToText('extrude(thickness, bracketLeg1Sketch')
|
||||
|
||||
// Ensure that a badge appears on the button
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
@ -129,59 +120,58 @@ extrude001 = extrude(5, sketch001)`
|
||||
await expect(page.locator('.cm-tooltip').first()).toBeVisible()
|
||||
})
|
||||
|
||||
test('When error is not in view you can click the badge to scroll to it', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
test.fixme(
|
||||
'When error is not in view you can click the badge to scroll to it',
|
||||
async ({ page, homePage, context }) => {
|
||||
// Load the app with the working starter code
|
||||
await context.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||
|
||||
// Load the app with the working starter code
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
// Ensure badge is present
|
||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
|
||||
// Ensure badge is present
|
||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
// Ensure we have no errors in the gutter, since error out of view.
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Ensure we have no errors in the gutter, since error out of view.
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
// Click the badge.
|
||||
const badge = page.locator('#code-badge')
|
||||
await expect(badge).toBeVisible()
|
||||
await badge.click()
|
||||
|
||||
// Click the badge.
|
||||
const badge = page.locator('#code-badge')
|
||||
await expect(badge).toBeVisible()
|
||||
await badge.click()
|
||||
// Ensure we have an error diagnostic.
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
|
||||
// Ensure we have an error diagnostic.
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
|
||||
// Hover over the error to see the error message
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(
|
||||
page
|
||||
.getByText(
|
||||
'sketch profile must lie entirely on one side of the revolution axis'
|
||||
)
|
||||
.first()
|
||||
).toBeVisible()
|
||||
})
|
||||
// Hover over the error to see the error message
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(
|
||||
page
|
||||
.getByText(
|
||||
'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
|
||||
)
|
||||
.first()
|
||||
).toBeVisible()
|
||||
}
|
||||
)
|
||||
|
||||
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
await page.addInitScript((code) => {
|
||||
await context.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
@ -241,32 +231,29 @@ extrude001 = extrude(5, sketch001)`
|
||||
test(
|
||||
'Opening multiple panes persists when switching projects',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
async ({ context, page }, testInfo) => {
|
||||
// Setup multiple projects.
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([
|
||||
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||
fsp.mkdir(bracketDir, { recursive: true }),
|
||||
])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([
|
||||
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||
fsp.mkdir(bracketDir, { recursive: true }),
|
||||
])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await test.step('Opening the bracket project should load', async () => {
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
@ -293,7 +280,7 @@ test(
|
||||
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
await expect(page.getByText('Create project')).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Opening the router-template project should load', async () => {
|
||||
@ -309,30 +296,21 @@ test(
|
||||
await expect(page.locator('#variables-pane')).toBeVisible()
|
||||
await expect(page.locator('#logs-pane')).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'external change of file contents are reflected in editor',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
async ({ context, page }, testInfo) => {
|
||||
const PROJECT_DIR_NAME = 'lee-was-here'
|
||||
const {
|
||||
electronApp,
|
||||
page,
|
||||
dir: projectsDir,
|
||||
} = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const aProjectDir = join(dir, PROJECT_DIR_NAME)
|
||||
await fsp.mkdir(aProjectDir, { recursive: true })
|
||||
},
|
||||
const { dir: projectsDir } = await context.folderSetupFn(async (dir) => {
|
||||
const aProjectDir = join(dir, PROJECT_DIR_NAME)
|
||||
await fsp.mkdir(aProjectDir, { recursive: true })
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await test.step('Open the project', async () => {
|
||||
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
|
||||
@ -351,7 +329,5 @@ test(
|
||||
)
|
||||
await u.editorTextMatches(content)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
@ -1,37 +1,30 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect } from './zoo-test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { getUtils } from './test-utils'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Command bar tests', () => {
|
||||
test('Extrude from command bar selects extrude line after', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
|> close(%)
|
||||
`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -52,51 +45,12 @@ test.describe('Command bar tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('Fillet from command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(-10, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const selectSegment = () => page.getByText(`line([0, -10], %)`).click()
|
||||
|
||||
await selectSegment()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByRole('button', { name: 'Fillet' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter') // skip selection
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter') // accept default radius
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter') // submit
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toContainText(
|
||||
`fillet({ radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] }, %)`
|
||||
)
|
||||
})
|
||||
|
||||
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
@ -153,7 +107,7 @@ extrude001 = extrude(-10, sketch001)`
|
||||
// Check that the visibility changed
|
||||
await expect(paneSelector).not.toBeVisible()
|
||||
|
||||
commandOptionInput = page.getByPlaceholder('off')
|
||||
commandOptionInput = page.locator('[id="option-input"]')
|
||||
|
||||
// Test case for https://github.com/KittyCAD/modeling-app/issues/2882
|
||||
await commandBarButton.click()
|
||||
@ -174,10 +128,10 @@ extrude001 = extrude(-10, sketch001)`
|
||||
|
||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
@ -221,25 +175,25 @@ extrude001 = extrude(-10, sketch001)`
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
})
|
||||
|
||||
test('Can extrude from the command bar', async ({ page }) => {
|
||||
test('Can extrude from the command bar', async ({ page, homePage }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`distance = sqrt(20)
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-6.95, 10.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -20.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
`
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-6.95, 10.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -20.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Make sure the stream is up
|
||||
await u.openDebugPanel()
|
||||
@ -293,26 +247,19 @@ extrude001 = extrude(-10, sketch001)`
|
||||
await continueButton.click()
|
||||
await submitButton.click()
|
||||
|
||||
// Check that the code was updated
|
||||
await u.waitForCmdReceive('extrude')
|
||||
// Unfortunately this indentation seems to matter for the test
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`distance = sqrt(20)
|
||||
distance001 = ${KCL_DEFAULT_LENGTH}
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-6.95, 10.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -20.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(distance001, sketch001)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
|
||||
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
'extrude001 = extrude(distance001, sketch001)'
|
||||
)
|
||||
})
|
||||
|
||||
test('Can switch between sketch tools via command bar', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
test('Can switch between sketch tools via command bar', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
||||
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
|
@ -1,23 +1,16 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { test, expect } from './zoo-test'
|
||||
import { getUtils } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
test.describe('Copilot ghost text', () => {
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
test.skip(true, 'Needs to get covered again')
|
||||
|
||||
test('completes code in empty file', async ({ page }) => {
|
||||
test('completes code in empty file', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -52,12 +45,13 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
test.skip('copilot disabled in sketch mode no select plane', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -101,12 +95,13 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
test('copilot disabled in sketch mode after selecting plane', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -184,12 +179,12 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('ArrowUp in code rejects the suggestion', async ({ page }) => {
|
||||
test('ArrowUp in code rejects the suggestion', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -212,12 +207,15 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('ArrowDown in code rejects the suggestion', async ({ page }) => {
|
||||
test('ArrowDown in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -240,12 +238,15 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('ArrowLeft in code rejects the suggestion', async ({ page }) => {
|
||||
test('ArrowLeft in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -268,12 +269,15 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('ArrowRight in code rejects the suggestion', async ({ page }) => {
|
||||
test('ArrowRight in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -296,12 +300,12 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('Enter in code scoots it down', async ({ page }) => {
|
||||
test('Enter in code scoots it down', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -326,12 +330,15 @@ test.describe('Copilot ghost text', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('Ctrl+shift+z in code rejects the suggestion', async ({ page }) => {
|
||||
test('Ctrl+shift+z in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -360,12 +367,13 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
test('Ctrl+z in code rejects the suggestion and undos the last code', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await page.waitForTimeout(800)
|
||||
await u.codeLocator.click()
|
||||
@ -420,98 +428,107 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
// TODO when we make codemirror a widget, we can test this.
|
||||
//await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
//await expect(page.locator('.cm-content')).toHaveText(``) })
|
||||
|
||||
test('delete in code rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
test('delete in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.press('Delete')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.press('Delete')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('backspace in code rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
test('backspace in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('focus outside code pane rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
test('focus outside code pane rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// Going outside the editor should hide the ghost text.
|
||||
await page.mouse.move(0, 0)
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
// Going outside the editor should hide the ghost text.
|
||||
await page.mouse.move(0, 0)
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,14 +1,6 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect } from './zoo-test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
import { getUtils } from './test-utils'
|
||||
|
||||
function countNewlines(input: string): number {
|
||||
let count = 0
|
||||
@ -24,13 +16,14 @@ test.describe('Debug pane', () => {
|
||||
test('Artifact IDs in the artifact graph are stable across code edits', async ({
|
||||
page,
|
||||
context,
|
||||
homePage,
|
||||
}) => {
|
||||
const code = `sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([1, 1], %)
|
||||
`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([1, 1], %)
|
||||
`
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
const tree = page.getByTestId('debug-feature-tree')
|
||||
const segment = tree.locator('li', {
|
||||
@ -39,7 +32,7 @@ test.describe('Debug pane', () => {
|
||||
})
|
||||
|
||||
await test.step('Test setup', async () => {
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.openKclCodePanel()
|
||||
await u.openDebugPanel()
|
||||
// Set the code in the code editor.
|
||||
|
@ -1,39 +1,31 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { join } from 'path'
|
||||
import { test, expect } from './zoo-test'
|
||||
import path from 'path'
|
||||
import {
|
||||
getUtils,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
getPlaywrightDownloadDir,
|
||||
} from './test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test(
|
||||
'export works on the first try',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(bracketDir, 'other.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
async ({ page, context }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
path.join(bracketDir, 'other.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
@ -93,12 +85,16 @@ test(
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
|
||||
const firstFileFullPath = path.resolve(
|
||||
getPlaywrightDownloadDir(page),
|
||||
exportFileName
|
||||
)
|
||||
await test.step('Check the export size', async () => {
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile(exportFileName)
|
||||
const outputGltf = await fsp.readFile(firstFileFullPath)
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
return 0
|
||||
@ -107,9 +103,6 @@ test(
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBeGreaterThan(300_000)
|
||||
|
||||
// clean up exported file
|
||||
await fsp.rm(exportFileName)
|
||||
})
|
||||
})
|
||||
|
||||
@ -170,12 +163,16 @@ test(
|
||||
expect(exportingToastMessage).not.toBeVisible(),
|
||||
]))
|
||||
|
||||
const secondFileFullPath = path.resolve(
|
||||
getPlaywrightDownloadDir(page),
|
||||
exportFileName
|
||||
)
|
||||
await test.step('Check the export size', async () => {
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile(exportFileName)
|
||||
const outputGltf = await fsp.readFile(secondFileFullPath)
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
return 0
|
||||
@ -184,13 +181,7 @@ test(
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBeGreaterThan(100_000)
|
||||
|
||||
// clean up exported file
|
||||
await fsp.rm(exportFileName)
|
||||
})
|
||||
await electronApp.close()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect } from './zoo-test'
|
||||
import fsp from 'fs/promises'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import {
|
||||
@ -6,37 +6,27 @@ import {
|
||||
darkModePlaneColorXZ,
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
|
||||
import { join } from 'path'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Editor tests', () => {
|
||||
test('can comment out code with ctrl+/', async ({ page }) => {
|
||||
test('can comment out code with ctrl+/', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('/')
|
||||
@ -44,11 +34,11 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
// |> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
// |> close(%)`)
|
||||
|
||||
// uncomment the code
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
@ -57,23 +47,22 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('if you click the format button it formats your code', async ({
|
||||
test('ensure we use the cache, and do not re-execute', async ({
|
||||
homePage,
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||
@ -82,36 +71,141 @@ test.describe('Editor tests', () => {
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
await page.locator('#code-pane button:first-child').click()
|
||||
await page.locator('button:has-text("Format code")').click()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||
// Ensure we execute the first time.
|
||||
await u.openDebugPanel()
|
||||
await expect(
|
||||
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||
).toHaveCount(1)
|
||||
await expect(
|
||||
page.locator('[data-message-type="execution-done"]')
|
||||
).toHaveCount(2)
|
||||
|
||||
// Add whitespace to the end of the code.
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('Home')
|
||||
await page.keyboard.type(' ')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(' ')
|
||||
|
||||
// Ensure we don't execute the second time.
|
||||
await u.openDebugPanel()
|
||||
// Make sure we didn't clear the scene.
|
||||
await expect(
|
||||
page.locator('[data-message-type="execution-done"]')
|
||||
).toHaveCount(3)
|
||||
await expect(
|
||||
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||
).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('ensure we use the cache, and do not clear on append', async ({
|
||||
homePage,
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
// Ensure we execute the first time.
|
||||
await u.openDebugPanel()
|
||||
await expect(
|
||||
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||
).toHaveCount(1)
|
||||
await expect(
|
||||
page.locator('[data-message-type="execution-done"]')
|
||||
).toHaveCount(2)
|
||||
|
||||
// Add whitespace to the end of the code.
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('const x = 1')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await u.openDebugPanel()
|
||||
await expect(
|
||||
page.locator('[data-message-type="execution-done"]')
|
||||
).toHaveCount(3)
|
||||
await expect(
|
||||
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||
).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('if you click the format button it formats your code', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
await page.locator('#code-pane button:first-child').click()
|
||||
await page.locator('button:has-text("Format code")').click()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('if you click the format button it formats your code and executes so lints are still there', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -135,11 +229,11 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
@ -151,29 +245,27 @@ test.describe('Editor tests', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('fold gutters work', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
test('fold gutters work', async ({ page, homePage }) => {
|
||||
const fullCode = `sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// TODO: Jess needs to fix this but you have to mod the code to get them to show
|
||||
// up, its an annoying codemirror thing.
|
||||
@ -224,22 +316,25 @@ test.describe('Editor tests', () => {
|
||||
await expect(foldGutterFoldLine).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('hover over functions shows function description', async ({ page }) => {
|
||||
test('hover over functions shows function description', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
@ -268,23 +363,24 @@ test.describe('Editor tests', () => {
|
||||
|
||||
test('if you use the format keyboard binding it formats your code', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
@ -301,32 +397,33 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('if you use the format keyboard binding it formats your code and executes so lints are shown', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -353,11 +450,11 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
@ -369,11 +466,14 @@ test.describe('Editor tests', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('if you write kcl with lint errors you get lints', async ({ page }) => {
|
||||
test('if you write kcl with lint errors you get lints', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
||||
@ -409,23 +509,26 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('if you fixup kcl errors you clear lints', async ({ page }) => {
|
||||
test('if you fixup kcl errors you clear lints', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> close(%)
|
||||
`
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
@ -447,22 +550,27 @@ test.describe('Editor tests', () => {
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||
test('if you write invalid kcl you get inlined errors', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
/* add the following code to the editor ($ error is not a valid line)
|
||||
$ error
|
||||
topAng = 30
|
||||
bottomAng = 25
|
||||
*/
|
||||
/* add the following code to the editor (~ error is not a valid line)
|
||||
* the old check here used $ but this is for tags so it changed meaning.
|
||||
* hopefully ~ doesn't change meaning
|
||||
~ error
|
||||
const topAng = 30
|
||||
const bottomAng = 25
|
||||
*/
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('$ error')
|
||||
await page.keyboard.type('~ error')
|
||||
|
||||
// press arrows to clear autocomplete
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
@ -474,17 +582,17 @@ test.describe('Editor tests', () => {
|
||||
await page.keyboard.type('bottomAng = 25')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// error in gutter
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(
|
||||
page.getByText('Tag names must not be empty').first()
|
||||
page.getByText("found unknown token '~'").first()
|
||||
).toBeVisible()
|
||||
|
||||
// select the line that's causing the error and delete it
|
||||
await page.getByText('$ error').click()
|
||||
await page.getByText('~ error').click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('Home')
|
||||
@ -520,106 +628,108 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
// TODO currently multiple source ranges are not supported
|
||||
test.skip('error with 2 source ranges gets 2 diagnostics', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`length = .750
|
||||
width = 0.500
|
||||
height = 0.500
|
||||
dia = 4
|
||||
test.fixme(
|
||||
'error with 2 source ranges gets 2 diagnostics',
|
||||
async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`length = .750
|
||||
width = 0.500
|
||||
height = 0.500
|
||||
dia = 4
|
||||
|
||||
fn squareHole = (l, w) => {
|
||||
squareHoleSketch = startSketchOn('XY')
|
||||
|> startProfileAt([-width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, length / 2], %)
|
||||
|> lineTo([-width / 2, length / 2], %)
|
||||
|> close(%)
|
||||
return squareHoleSketch
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
fn squareHole = (l, w) => {
|
||||
squareHoleSketch = startSketchOn('XY')
|
||||
|> startProfileAt([-width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, length / 2], %)
|
||||
|> lineTo([-width / 2, length / 2], %)
|
||||
|> close(%)
|
||||
return squareHoleSketch
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Click on the bottom of the code editor to add a new line
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
||||
|> circle({ center = [0, 0], radius = dia/2 }, %)
|
||||
|> hole(squareHole(length, width, height), %)
|
||||
|> extrude(height, %)`)
|
||||
// Click on the bottom of the code editor to add a new line
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
||||
|> circle({ center: [0, 0], radius: dia/2 }, %)
|
||||
|> hole(squareHole(length, width, height), %)
|
||||
|> extrude(height, %)`)
|
||||
|
||||
// error in gutter
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
await page.hover('.cm-lint-marker-error:first-child')
|
||||
await expect(
|
||||
page.getByText('Expected 2 arguments, got 3').first()
|
||||
).toBeVisible()
|
||||
// error in gutter
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
await page.hover('.cm-lint-marker-error:first-child')
|
||||
await expect(
|
||||
page.getByText('Expected 2 arguments, got 3').first()
|
||||
).toBeVisible()
|
||||
|
||||
// Make sure there are two diagnostics
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
||||
})
|
||||
// Make sure there are two diagnostics
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
||||
}
|
||||
)
|
||||
test('if your kcl gets an error from the engine it is inlined', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`box = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %, $revolveAxis)
|
||||
|> close(%)
|
||||
|> extrude(10, %)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %, $revolveAxis)
|
||||
|> close(%)
|
||||
|> extrude(10, %)
|
||||
|
||||
sketch001 = startSketchOn(box, revolveAxis)
|
||||
|> startProfileAt([5, 10], %)
|
||||
|> line([0, -10], %)
|
||||
|> line([2, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> close(%)
|
||||
|> revolve({
|
||||
axis = revolveAxis,
|
||||
angle = 90
|
||||
}, %)
|
||||
`
|
||||
sketch001 = startSketchOn(box, revolveAxis)
|
||||
|> startProfileAt([5, 10], %)
|
||||
|> line([0, -10], %)
|
||||
|> line([2, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> close(%)
|
||||
|> revolve({
|
||||
axis: revolveAxis,
|
||||
angle: 90
|
||||
}, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await page.goto('/')
|
||||
await u.waitForPageLoad()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
@ -630,12 +740,15 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.getByText(searchText)).toBeVisible()
|
||||
})
|
||||
test.describe('Autocomplete works', () => {
|
||||
test('with enter/click to accept the completion', async ({ page }) => {
|
||||
test('with enter/click to accept the completion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// tests clicking on an option, selection the first option
|
||||
// and arrowing down to an option
|
||||
@ -697,19 +810,19 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
|
||||
// expect there to be no KCL errors
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('with tab to accept the completion', async ({ page }) => {
|
||||
test('with tab to accept the completion', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// this test might be brittle as we add and remove functions
|
||||
// but should also be easy to update.
|
||||
@ -771,26 +884,30 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
})
|
||||
})
|
||||
test('Can undo a click and point extrude with ctrl+z', async ({ page }) => {
|
||||
test('Can undo a click and point extrude with ctrl+z', async ({
|
||||
page,
|
||||
context,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -843,29 +960,32 @@ test.describe('Editor tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('Can undo a sketch modification with ctrl+z', async ({ page }) => {
|
||||
test('Can undo a sketch modification with ctrl+z', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -892,7 +1012,7 @@ test.describe('Editor tests', () => {
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [665, 397]
|
||||
const startPX = [1200 / 2, 500 / 2]
|
||||
|
||||
const dragPX = 40
|
||||
|
||||
@ -906,9 +1026,9 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
// drag startProfileAt handle
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: startPX[0], y: startPX[1] },
|
||||
sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 },
|
||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
@ -946,12 +1066,12 @@ test.describe('Editor tests', () => {
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([15.39, -2.78], %)
|
||||
|> tangentialArcTo([27.6, -3.05], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line([15.4, -2.78], %)
|
||||
|> tangentialArcTo([27.6, -3.05], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|
||||
// Hit undo
|
||||
await page.keyboard.down('Control')
|
||||
@ -960,11 +1080,11 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([15.39, -2.78], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line([15.4, -2.78], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
@ -973,12 +1093,12 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
@ -988,31 +1108,29 @@ test.describe('Editor tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
})
|
||||
|
||||
test.fixme(
|
||||
`Can use the import stdlib function on a local OBJ file`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'cube')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.obj'),
|
||||
join(bracketDir, 'cube.obj')
|
||||
)
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
|
||||
},
|
||||
async ({ page, context }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'cube')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.obj'),
|
||||
join(bracketDir, 'cube.obj')
|
||||
)
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
|
||||
})
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
await page.setBodyDimensions(viewportSize)
|
||||
|
||||
// Locators and constants
|
||||
const u = await getUtils(page)
|
||||
@ -1070,8 +1188,6 @@ test.describe('Editor tests', () => {
|
||||
})
|
||||
.toBeGreaterThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
127
e2e/playwright/feature-tree-pane.spec.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
|
||||
const FEATURE_TREE_EXAMPLE_CODE = `export fn timesFive(x) {
|
||||
return 5 * x
|
||||
}
|
||||
export fn triangle() {
|
||||
return startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> xLine(10, %)
|
||||
|> line([-10, -5], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
}
|
||||
|
||||
length001 = timesFive(1) * 5
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([20, 10], %)
|
||||
|> line([10, 10], %)
|
||||
|> angledLine([-45, length001], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
revolve001 = revolve({ axis = "X" }, sketch001)
|
||||
triangle()
|
||||
|> extrude(30, %)
|
||||
plane001 = offsetPlane('XY', 10)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> startProfileAt([-20, 0], %)
|
||||
|> line([5, -15], %)
|
||||
|> xLine(-10, %)
|
||||
|> lineTo([-40, 0], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(10, sketch002)
|
||||
`
|
||||
|
||||
test.describe('Feature Tree pane', () => {
|
||||
test(
|
||||
'User can go to definition and go to function definition',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, homePage, scene, editor, toolbar }) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'test-sample')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.writeFile(
|
||||
join(bracketDir, 'main.kcl'),
|
||||
FEATURE_TREE_EXAMPLE_CODE,
|
||||
'utf-8'
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('setup test', async () => {
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: 'test-sample',
|
||||
fileCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.openProject('test-sample')
|
||||
await scene.waitForExecutionDone()
|
||||
await editor.closePane()
|
||||
await toolbar.openFeatureTreePane()
|
||||
})
|
||||
|
||||
async function testViewSource({
|
||||
operationName,
|
||||
operationIndex,
|
||||
expectedActiveLine,
|
||||
}: {
|
||||
operationName: string
|
||||
operationIndex: number
|
||||
expectedActiveLine: string
|
||||
}) {
|
||||
await test.step(`Go to definition of the ${operationName}`, async () => {
|
||||
await toolbar.viewSourceOnOperation(operationName, operationIndex)
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
diagnostics: [],
|
||||
activeLines: [expectedActiveLine],
|
||||
})
|
||||
await expect(
|
||||
editor.activeLine.first(),
|
||||
`${operationName} code should be scrolled into view`
|
||||
).toBeVisible()
|
||||
})
|
||||
}
|
||||
|
||||
await testViewSource({
|
||||
operationName: 'Offset Plane',
|
||||
operationIndex: 0,
|
||||
expectedActiveLine: "plane001 = offsetPlane('XY', 10)",
|
||||
})
|
||||
await testViewSource({
|
||||
operationName: 'Extrude',
|
||||
operationIndex: 1,
|
||||
expectedActiveLine: 'extrude001 = extrude(10, sketch002)',
|
||||
})
|
||||
await testViewSource({
|
||||
operationName: 'Revolve',
|
||||
operationIndex: 0,
|
||||
expectedActiveLine: 'revolve001 = revolve({ axis = "X" }, sketch001)',
|
||||
})
|
||||
await testViewSource({
|
||||
operationName: 'Triangle',
|
||||
operationIndex: 0,
|
||||
expectedActiveLine: 'triangle()',
|
||||
})
|
||||
|
||||
await test.step('Go to definition on the triangle function', async () => {
|
||||
await toolbar.goToDefinitionOnOperation('Triangle', 0)
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
diagnostics: [],
|
||||
activeLines: ['export fn triangle() {'],
|
||||
})
|
||||
await expect(
|
||||
editor.activeLine.first(),
|
||||
'Triangle function definition should be scrolled into view'
|
||||
).toBeVisible()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
@ -1,4 +1,4 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
type CmdBarSerialised =
|
||||
@ -26,9 +26,11 @@ type CmdBarSerialised =
|
||||
|
||||
export class CmdBarFixture {
|
||||
public page: Page
|
||||
cmdBarOpenBtn!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.cmdBarOpenBtn = page.getByTestId('command-bar-open-button')
|
||||
}
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
@ -116,4 +118,37 @@ export class CmdBarFixture {
|
||||
await this.page.keyboard.press('Enter')
|
||||
}
|
||||
}
|
||||
|
||||
openCmdBar = async (selectCmd?: 'promptToEdit') => {
|
||||
// TODO why does this button not work in electron tests?
|
||||
// await this.cmdBarOpenBtn.click()
|
||||
await this.page.keyboard.down('ControlOrMeta')
|
||||
await this.page.keyboard.press('KeyK')
|
||||
await this.page.keyboard.up('ControlOrMeta')
|
||||
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
|
||||
if (selectCmd === 'promptToEdit') {
|
||||
const promptEditCommand = this.page.getByText(
|
||||
'Use Zoo AI to edit your kcl'
|
||||
)
|
||||
await expect(promptEditCommand.first()).toBeVisible()
|
||||
await promptEditCommand.first().scrollIntoViewIfNeeded()
|
||||
await promptEditCommand.first().click()
|
||||
}
|
||||
}
|
||||
|
||||
get cmdSearchInput() {
|
||||
return this.page.getByTestId('cmd-bar-search')
|
||||
}
|
||||
|
||||
get argumentInput() {
|
||||
return this.page.getByTestId('cmd-bar-arg-value')
|
||||
}
|
||||
|
||||
get cmdOptions() {
|
||||
return this.page.getByTestId('cmd-bar-option')
|
||||
}
|
||||
|
||||
chooseCommand = async (commandName: string) => {
|
||||
await this.cmdOptions.getByText(commandName).click()
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ export class EditorFixture {
|
||||
private diagnosticsTooltip!: Locator
|
||||
private diagnosticsGutterIcon!: Locator
|
||||
private codeContent!: Locator
|
||||
private activeLine!: Locator
|
||||
public activeLine!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
@ -29,7 +29,7 @@ export class EditorFixture {
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
|
||||
this.codeContent = page.locator('.cm-content')
|
||||
this.codeContent = page.locator('.cm-content[data-language="kcl"]')
|
||||
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
||||
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
|
||||
this.activeLine = this.page.locator('.cm-activeLine')
|
||||
@ -54,13 +54,13 @@ export class EditorFixture {
|
||||
}
|
||||
}
|
||||
if (!shouldNormalise) {
|
||||
const expectStart = expect(this.codeContent)
|
||||
const expectStart = expect.poll(() => this.codeContent.textContent())
|
||||
if (not) {
|
||||
const result = await expectStart.not.toContainText(code, { timeout })
|
||||
const result = await expectStart.not.toContain(code)
|
||||
await resetPane()
|
||||
return result
|
||||
}
|
||||
const result = await expectStart.toContainText(code, { timeout })
|
||||
const result = await expectStart.toContain(code)
|
||||
await resetPane()
|
||||
return result
|
||||
}
|
||||
@ -147,4 +147,28 @@ export class EditorFixture {
|
||||
openPane() {
|
||||
return openPane(this.page, this.paneButtonTestId)
|
||||
}
|
||||
scrollToText(text: string, placeCursor?: boolean) {
|
||||
return this.page.evaluate(
|
||||
(args: { text: string; placeCursor?: boolean }) => {
|
||||
// error TS2339: Property 'docView' does not exist on type 'EditorView'.
|
||||
// Except it does so :shrug:
|
||||
// @ts-ignore
|
||||
let index = window.editorManager._editorView?.docView.view.state.doc
|
||||
.toString()
|
||||
.indexOf(args.text)
|
||||
window.editorManager._editorView?.focus()
|
||||
window.editorManager._editorView?.dispatch({
|
||||
selection: window.EditorSelection.create([
|
||||
window.EditorSelection.cursor(index),
|
||||
]),
|
||||
effects: [
|
||||
window.EditorView.scrollIntoView(
|
||||
window.EditorSelection.range(index, index + 1)
|
||||
),
|
||||
],
|
||||
})
|
||||
},
|
||||
{ text, placeCursor }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import type {
|
||||
BrowserContext,
|
||||
ElectronApplication,
|
||||
Page,
|
||||
TestInfo,
|
||||
Page,
|
||||
} from '@playwright/test'
|
||||
import { test as base } from '@playwright/test'
|
||||
import { getUtils, setup, setupElectron, tearDown } from '../test-utils'
|
||||
|
||||
import { getUtils, setup, setupElectron } from '../test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { CmdBarFixture } from './cmdBarFixture'
|
||||
@ -20,11 +20,13 @@ export class AuthenticatedApp {
|
||||
public readonly page: Page
|
||||
public readonly context: BrowserContext
|
||||
public readonly testInfo: TestInfo
|
||||
public readonly viewPortSize = { width: 1000, height: 500 }
|
||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
||||
public electronApp: undefined | ElectronApplication
|
||||
public dir: string = ''
|
||||
|
||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||
this.page = page
|
||||
this.context = context
|
||||
this.page = page
|
||||
this.testInfo = testInfo
|
||||
}
|
||||
|
||||
@ -49,9 +51,7 @@ export class AuthenticatedApp {
|
||||
}
|
||||
}
|
||||
|
||||
interface Fixtures {
|
||||
app: AuthenticatedApp
|
||||
tronApp: AuthenticatedTronApp
|
||||
export interface Fixtures {
|
||||
cmdBar: CmdBarFixture
|
||||
editor: EditorFixture
|
||||
toolbar: ToolbarFixture
|
||||
@ -61,9 +61,11 @@ interface Fixtures {
|
||||
export class AuthenticatedTronApp {
|
||||
public readonly _page: Page
|
||||
public page: Page
|
||||
public readonly context: BrowserContext
|
||||
public context: BrowserContext
|
||||
public readonly testInfo: TestInfo
|
||||
public electronApp?: ElectronApplication
|
||||
public electronApp: ElectronApplication | undefined
|
||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
||||
public dir: string = ''
|
||||
|
||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||
this._page = page
|
||||
@ -79,15 +81,22 @@ export class AuthenticatedTronApp {
|
||||
appSettings?: Partial<SaveSettingsPayload>
|
||||
} = { fixtures: {} }
|
||||
) {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
const { electronApp, page, context, dir } = await setupElectron({
|
||||
testInfo: this.testInfo,
|
||||
folderSetupFn: arg.folderSetupFn,
|
||||
cleanProjectDir: arg.cleanProjectDir,
|
||||
appSettings: arg.appSettings,
|
||||
})
|
||||
this.page = page
|
||||
this.context = context
|
||||
this.electronApp = electronApp
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
this.dir = dir
|
||||
|
||||
// Easier to access throughout utils
|
||||
this.page.dir = dir
|
||||
|
||||
// Setup localStorage, addCookies, reload
|
||||
await setup(this.context, this.page, this.testInfo)
|
||||
|
||||
for (const key of unsafeTypedKeys(arg.fixtures)) {
|
||||
const fixture = arg.fixtures[key]
|
||||
@ -110,32 +119,25 @@ export class AuthenticatedTronApp {
|
||||
})
|
||||
}
|
||||
|
||||
export const test = base.extend<Fixtures>({
|
||||
app: async ({ page, context }, use, testInfo) => {
|
||||
await use(new AuthenticatedApp(context, page, testInfo))
|
||||
},
|
||||
tronApp: async ({ page, context }, use, testInfo) => {
|
||||
await use(new AuthenticatedTronApp(context, page, testInfo))
|
||||
},
|
||||
cmdBar: async ({ page }, use) => {
|
||||
export const fixtures = {
|
||||
cmdBar: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new CmdBarFixture(page))
|
||||
},
|
||||
editor: async ({ page }, use) => {
|
||||
editor: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new EditorFixture(page))
|
||||
},
|
||||
toolbar: async ({ page }, use) => {
|
||||
toolbar: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new ToolbarFixture(page))
|
||||
},
|
||||
scene: async ({ page }, use) => {
|
||||
scene: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new SceneFixture(page))
|
||||
},
|
||||
homePage: async ({ page }, use) => {
|
||||
homePage: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new HomePageFixture(page))
|
||||
},
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
export { expect } from '@playwright/test'
|
||||
}
|
||||
|
@ -14,10 +14,14 @@ interface HomePageState {
|
||||
export class HomePageFixture {
|
||||
public page: Page
|
||||
|
||||
projectSection!: Locator
|
||||
projectCard!: Locator
|
||||
projectCardTitle!: Locator
|
||||
projectCardFile!: Locator
|
||||
projectCardFolder!: Locator
|
||||
projectButtonNew!: Locator
|
||||
projectButtonContinue!: Locator
|
||||
projectTextName!: Locator
|
||||
sortByDateBtn!: Locator
|
||||
sortByNameBtn!: Locator
|
||||
|
||||
@ -28,11 +32,19 @@ export class HomePageFixture {
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
|
||||
this.projectSection = this.page.getByTestId('home-section')
|
||||
|
||||
this.projectCard = this.page.getByTestId('project-link')
|
||||
this.projectCardTitle = this.page.getByTestId('project-title')
|
||||
this.projectCardFile = this.page.getByTestId('project-file-count')
|
||||
this.projectCardFolder = this.page.getByTestId('project-folder-count')
|
||||
|
||||
this.projectButtonNew = this.page.getByTestId('home-new-file')
|
||||
this.projectTextName = this.page.getByTestId('cmd-bar-arg-value')
|
||||
this.projectButtonContinue = this.page.getByRole('button', {
|
||||
name: 'Continue',
|
||||
})
|
||||
|
||||
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||
}
|
||||
@ -91,10 +103,25 @@ export class HomePageFixture {
|
||||
.toEqual(expectedState)
|
||||
}
|
||||
|
||||
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
|
||||
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
|
||||
await this.projectButtonNew.click()
|
||||
await this.projectTextName.click()
|
||||
await this.projectTextName.fill(projectTitle)
|
||||
await this.projectButtonContinue.click()
|
||||
}
|
||||
|
||||
openProject = async (projectTitle: string) => {
|
||||
const projectCard = this.projectCard.locator(
|
||||
this.page.getByText(projectTitle)
|
||||
)
|
||||
await projectCard.click()
|
||||
}
|
||||
|
||||
goToModelingScene = async (name: string = 'testDefault') => {
|
||||
// On web this is a no-op. There is no project view.
|
||||
if (process.env.PLATFORM === 'web') return
|
||||
|
||||
await this.createAndGoToProject(name)
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,8 @@ type DragFromHandler = (
|
||||
|
||||
export class SceneFixture {
|
||||
public page: Page
|
||||
|
||||
public streamWrapper!: Locator
|
||||
public loadingIndicator!: Locator
|
||||
private exeIndicator!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
@ -53,8 +54,9 @@ export class SceneFixture {
|
||||
|
||||
expectState = async (expected: SceneSerialised) => {
|
||||
return expect
|
||||
.poll(() => this._serialiseScene(), {
|
||||
message: `Expected scene state to match`,
|
||||
.poll(async () => await this._serialiseScene(), {
|
||||
intervals: [1_000, 2_000, 10_000],
|
||||
timeout: 60000,
|
||||
})
|
||||
.toEqual(expected)
|
||||
}
|
||||
@ -63,6 +65,8 @@ export class SceneFixture {
|
||||
this.page = page
|
||||
|
||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||
this.streamWrapper = page.getByTestId('stream')
|
||||
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
|
||||
}
|
||||
|
||||
makeMouseHelpers = (
|
||||
@ -187,7 +191,10 @@ export class SceneFixture {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await this.waitForExecutionDone()
|
||||
await this.page
|
||||
.locator(`[data-receive-command-type="default_camera_get_settings"]`)
|
||||
.first()
|
||||
.waitFor()
|
||||
const position = await Promise.all([
|
||||
this.page.getByTestId('cam-x-position').inputValue().then(Number),
|
||||
this.page.getByTestId('cam-y-position').inputValue().then(Number),
|
||||
@ -214,23 +221,7 @@ export class SceneFixture {
|
||||
coords: { x: number; y: number },
|
||||
diff: number
|
||||
) => {
|
||||
let finalValue = colour
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const pixel = (await getPixelRGBs(this.page)(coords, 1))[0]
|
||||
if (!pixel) return null
|
||||
finalValue = pixel
|
||||
return pixel.every(
|
||||
(channel, index) => Math.abs(channel - colour[index]) < diff
|
||||
)
|
||||
})
|
||||
.toBeTruthy()
|
||||
.catch((cause) => {
|
||||
throw new Error(
|
||||
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
||||
{ cause }
|
||||
)
|
||||
})
|
||||
await expectPixelColor(this.page, colour, coords, diff)
|
||||
}
|
||||
|
||||
get gizmo() {
|
||||
@ -238,6 +229,7 @@ export class SceneFixture {
|
||||
}
|
||||
|
||||
async clickGizmoMenuItem(name: string) {
|
||||
await this.gizmo.hover()
|
||||
await this.gizmo.click({ button: 'right' })
|
||||
const buttonToTest = this.page.getByRole('button', {
|
||||
name: name,
|
||||
@ -246,3 +238,28 @@ export class SceneFixture {
|
||||
await buttonToTest.click()
|
||||
}
|
||||
}
|
||||
|
||||
export async function expectPixelColor(
|
||||
page: Page,
|
||||
colour: [number, number, number],
|
||||
coords: { x: number; y: number },
|
||||
diff: number
|
||||
) {
|
||||
let finalValue = colour
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
||||
if (!pixel) return null
|
||||
finalValue = pixel
|
||||
return pixel.every(
|
||||
(channel, index) => Math.abs(channel - colour[index]) < diff
|
||||
)
|
||||
})
|
||||
.toBeTruthy()
|
||||
.catch((cause) => {
|
||||
throw new Error(
|
||||
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
||||
{ cause }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -1,12 +1,23 @@
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { expect } from './fixtureSetup'
|
||||
import { doAndWaitForImageDiff } from '../test-utils'
|
||||
import { expect } from '../zoo-test'
|
||||
import {
|
||||
checkIfPaneIsOpen,
|
||||
closePane,
|
||||
doAndWaitForImageDiff,
|
||||
openPane,
|
||||
} from '../test-utils'
|
||||
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
|
||||
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
|
||||
|
||||
export class ToolbarFixture {
|
||||
public page: Page
|
||||
|
||||
extrudeButton!: Locator
|
||||
loftButton!: Locator
|
||||
sweepButton!: Locator
|
||||
filletButton!: Locator
|
||||
chamferButton!: Locator
|
||||
shellButton!: Locator
|
||||
offsetPlaneButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
lineBtn!: Locator
|
||||
@ -19,6 +30,10 @@ export class ToolbarFixture {
|
||||
filePane!: Locator
|
||||
exeIndicator!: Locator
|
||||
treeInputField!: Locator
|
||||
/** The sidebar button for the Feature Tree pane */
|
||||
featureTreeId = 'feature-tree' as const
|
||||
/** The pane element for the Feature Tree */
|
||||
featureTreePane!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
@ -28,6 +43,10 @@ export class ToolbarFixture {
|
||||
this.page = page
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.loftButton = page.getByTestId('loft')
|
||||
this.sweepButton = page.getByTestId('sweep')
|
||||
this.filletButton = page.getByTestId('fillet3d')
|
||||
this.chamferButton = page.getByTestId('chamfer3d')
|
||||
this.shellButton = page.getByTestId('shell')
|
||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.lineBtn = page.getByTestId('line')
|
||||
@ -39,10 +58,15 @@ export class ToolbarFixture {
|
||||
this.treeInputField = page.getByTestId('tree-input-field')
|
||||
|
||||
this.filePane = page.locator('#files-pane')
|
||||
this.featureTreePane = page.locator('#feature-tree-pane')
|
||||
this.fileCreateToast = page.getByText('Successfully created')
|
||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||
}
|
||||
|
||||
get logoLink() {
|
||||
return this.page.getByTestId('app-logo')
|
||||
}
|
||||
|
||||
startSketchPlaneSelection = async () =>
|
||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||
|
||||
@ -89,4 +113,76 @@ export class ToolbarFixture {
|
||||
await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 })
|
||||
}
|
||||
}
|
||||
|
||||
async closePane(paneId: SidebarType) {
|
||||
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||
}
|
||||
async openPane(paneId: SidebarType) {
|
||||
return openPane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||
}
|
||||
async checkIfPaneIsOpen(paneId: SidebarType) {
|
||||
return checkIfPaneIsOpen(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||
}
|
||||
|
||||
async openFeatureTreePane() {
|
||||
return this.openPane(this.featureTreeId)
|
||||
}
|
||||
async closeFeatureTreePane() {
|
||||
await this.closePane(this.featureTreeId)
|
||||
}
|
||||
async checkIfFeatureTreePaneIsOpen() {
|
||||
return this.checkIfPaneIsOpen(this.featureTreeId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific operation button from the Feature Tree pane
|
||||
*/
|
||||
async getFeatureTreeOperation(operationName: string, operationIndex: number) {
|
||||
await this.openFeatureTreePane()
|
||||
await expect(this.featureTreePane).toBeVisible()
|
||||
return this.featureTreePane
|
||||
.getByRole('button', {
|
||||
name: operationName,
|
||||
})
|
||||
.nth(operationIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
* View source on a specific operation in the Feature Tree pane.
|
||||
* @param operationName The name of the operation type
|
||||
* @param operationIndex The index out of operations of this type
|
||||
*/
|
||||
async viewSourceOnOperation(operationName: string, operationIndex: number) {
|
||||
const operationButton = await this.getFeatureTreeOperation(
|
||||
operationName,
|
||||
operationIndex
|
||||
)
|
||||
const viewSourceMenuButton = this.page.getByRole('button', {
|
||||
name: 'View KCL source code',
|
||||
})
|
||||
|
||||
await operationButton.click({ button: 'right' })
|
||||
await expect(viewSourceMenuButton).toBeVisible()
|
||||
await viewSourceMenuButton.click()
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to definition on a specific operation in the Feature Tree pane
|
||||
*/
|
||||
async goToDefinitionOnOperation(
|
||||
operationName: string,
|
||||
operationIndex: number
|
||||
) {
|
||||
const operationButton = await this.getFeatureTreeOperation(
|
||||
operationName,
|
||||
operationIndex
|
||||
)
|
||||
const goToDefinitionMenuButton = this.page.getByRole('button', {
|
||||
name: 'View function definition',
|
||||
})
|
||||
|
||||
await operationButton.click({ button: 'right' })
|
||||
await expect(goToDefinitionMenuButton).toBeVisible()
|
||||
await goToDefinitionMenuButton.click()
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,22 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { setupElectron, tearDown, executorInputPath } from './test-utils'
|
||||
import { test, expect } from './zoo-test'
|
||||
import { executorInputPath } from './test-utils'
|
||||
import { join } from 'path'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test(
|
||||
'When machine-api server not found butt is disabled and shows the reason',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
@ -47,28 +40,23 @@ test(
|
||||
// that the machine-api server is not found
|
||||
await makeButton.hover()
|
||||
await expect(page.getByText(notFoundText).first()).toBeVisible()
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'When machine-api server not found home screen & project status shows the reason',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
const notFoundText = 'Machine API server was not discovered'
|
||||
|
||||
@ -91,7 +79,5 @@ test(
|
||||
|
||||
await networkMachineToggle.hover()
|
||||
await expect(page.getByText(notFoundText).nth(1)).toBeVisible()
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
12
e2e/playwright/null.spec.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// These tests are meant to simply test starting and stopping the electron
|
||||
// application, check it can make it to the project pane, and nothing more.
|
||||
// It also tests our test wrappers are working.
|
||||
// Additionally this serves as a nice minimal example.
|
||||
|
||||
import { test, expect } from './zoo-test'
|
||||
|
||||
test.describe('Open the application', () => {
|
||||
test('see the project view', async ({ page, context }) => {
|
||||
await expect(page.getByTestId('home-section')).toBeVisible()
|
||||
})
|
||||
})
|
@ -1,79 +1,78 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect } from './zoo-test'
|
||||
import { join } from 'path'
|
||||
import fsp from 'fs/promises'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
createProject,
|
||||
} from './test-utils'
|
||||
import { getUtils, executorInputPath, createProject } from './test-utils'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import {
|
||||
TEST_SETTINGS_KEY,
|
||||
TEST_SETTINGS_ONBOARDING_START,
|
||||
TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||
TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING,
|
||||
TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||
} from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
import { expectPixelColor } from './fixtures/sceneFixture'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
if (testInfo.tags.includes('@electron')) {
|
||||
return
|
||||
}
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
// Because onboarding relies on an app setting we need to set it as incompletel
|
||||
// for all these tests.
|
||||
|
||||
test.describe('Onboarding tests', () => {
|
||||
test('Onboarding code is shown in the editor', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey }) => {
|
||||
// Give no initial code, so that the onboarding start is shown immediately
|
||||
localStorage.removeItem('persistCode')
|
||||
localStorage.removeItem(settingsKey)
|
||||
test(
|
||||
'Onboarding code is shown in the editor',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
{ settingsKey: TEST_SETTINGS_KEY }
|
||||
)
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
// Test that the onboarding pane loaded
|
||||
await expect(
|
||||
page.getByText('Welcome to Modeling App! This')
|
||||
).toBeVisible()
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
// Test that the onboarding pane loaded
|
||||
await expect(
|
||||
page.getByText('Welcome to Modeling App! This')
|
||||
).toBeVisible()
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||
// *and* that the code is shown in the editor
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
'// Shelf Bracket'
|
||||
)
|
||||
|
||||
// *and* that the code is shown in the editor
|
||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||
})
|
||||
// Make sure the model loaded
|
||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||
const modelColor: [number, number, number] = [45, 45, 45]
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(
|
||||
8
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Desktop: fresh onboarding executes and loads',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
{
|
||||
tag: '@electron',
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
})
|
||||
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ page, homePage }, testInfo) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
await page.setBodyDimensions(viewportSize)
|
||||
|
||||
await test.step(`Create a project and open to the onboarding`, async () => {
|
||||
await createProject({ name: 'project-link', page })
|
||||
@ -92,321 +91,370 @@ test.describe('Onboarding tests', () => {
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
'// Shelf Bracket'
|
||||
)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
// TODO: jess make less shit
|
||||
// Make sure the model loaded
|
||||
//const XYPlanePoint = { x: 986, y: 522 } as const
|
||||
//const modelColor: [number, number, number] = [76, 76, 76]
|
||||
//await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
|
||||
//await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test('Code resets after confirmation', async ({ page }) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')`
|
||||
|
||||
// Load the page up with some code so we see the confirmation warning
|
||||
// when we go to replay onboarding
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, initialCode)
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Replay the onboarding
|
||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||
const replayButton = page.getByRole('button', { name: 'Replay onboarding' })
|
||||
await expect(replayButton).toBeVisible()
|
||||
await replayButton.click()
|
||||
|
||||
// Ensure we see the warning, and that the code has not yet updated
|
||||
await expect(
|
||||
page.getByText('Replaying onboarding resets your code')
|
||||
).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(initialCode)
|
||||
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
await expect(nextButton).toBeVisible()
|
||||
await nextButton.click()
|
||||
|
||||
// Ensure we see the introduction and that the code has been reset
|
||||
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||
|
||||
// Ensure we persisted the code to local storage.
|
||||
// Playwright's addInitScript method unfortunately will reset
|
||||
// this code if we try reloading the page as a test,
|
||||
// so this is our best way to test persistence afaik.
|
||||
expect(
|
||||
await page.evaluate(() => {
|
||||
return localStorage.getItem('persistCode')
|
||||
})
|
||||
).toContain('// Shelf Bracket')
|
||||
})
|
||||
|
||||
test('Click through each onboarding step', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
// Give no initial code, so that the onboarding start is shown immediately
|
||||
localStorage.setItem('persistCode', '')
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
test(
|
||||
'Code resets after confirmation',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }),
|
||||
}
|
||||
)
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')`
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||
// Load the page up with some code so we see the confirmation warning
|
||||
// when we go to replay onboarding
|
||||
await context.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, initialCode)
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||
// Replay the onboarding
|
||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||
const replayButton = page.getByRole('button', {
|
||||
name: 'Replay onboarding',
|
||||
})
|
||||
await expect(replayButton).toBeVisible()
|
||||
await replayButton.click()
|
||||
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
// Ensure we see the warning, and that the code has not yet updated
|
||||
await expect(page.getByText('Would you like to create')).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(initialCode)
|
||||
|
||||
while ((await nextButton.innerText()) !== 'Finish') {
|
||||
await expect(nextButton).toBeVisible()
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
await nextButton.hover()
|
||||
await nextButton.click()
|
||||
|
||||
// Ensure we see the introduction and that the code has been reset
|
||||
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
'// Shelf Bracket'
|
||||
)
|
||||
|
||||
// There used to be old code here that checked if we stored the reset
|
||||
// code into localStorage but that isn't the case on desktop. It gets
|
||||
// saved to the file system, which we have other tests for.
|
||||
}
|
||||
)
|
||||
|
||||
// Finish the onboarding
|
||||
await expect(nextButton).toBeVisible()
|
||||
await nextButton.click()
|
||||
|
||||
// Test that the onboarding pane is gone
|
||||
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||
await expect(page.url()).not.toContain('onboarding')
|
||||
})
|
||||
|
||||
test('Onboarding redirects and code updating', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
// Give some initial code, so we can test that it's cleared
|
||||
localStorage.setItem('persistCode', 'sigmaAllow = 15000')
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
test(
|
||||
'Click through each onboarding step',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }),
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
// Override beforeEach test setup
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
// Give no initial code, so that the onboarding start is shown immediately
|
||||
localStorage.setItem('persistCode', '')
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: TEST_SETTINGS_ONBOARDING_START,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 1080 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
await expect(
|
||||
page.getByText('Welcome to Modeling App! This')
|
||||
).toBeVisible()
|
||||
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
|
||||
while ((await nextButton.innerText()) !== 'Finish') {
|
||||
await nextButton.hover()
|
||||
await nextButton.click()
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
// Finish the onboarding
|
||||
await nextButton.hover()
|
||||
await nextButton.click()
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Test that the redirect happened
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
)
|
||||
|
||||
// Test that you come back to this page when you refresh
|
||||
await page.reload()
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
)
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
const title = page.locator('[data-testid="onboarding-content"]')
|
||||
await expect(title).toBeAttached()
|
||||
|
||||
// Test that the code changes when you advance to the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await expect(page.locator('.cm-content')).toHaveText('')
|
||||
|
||||
// Test that the code is not empty when you click on the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||
})
|
||||
|
||||
test('Onboarding code gets reset to demo on Interactive Numbers step', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip(
|
||||
process.platform === 'darwin',
|
||||
"Skip on macOS, because Playwright isn't behaving the same as the actual browser"
|
||||
)
|
||||
const u = await getUtils(page)
|
||||
const badCode = `// This is bad code we shouldn't see`
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings, badCode }) => {
|
||||
localStorage.setItem('persistCode', badCode)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING,
|
||||
}),
|
||||
badCode,
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForURL('**' + onboardingPaths.PARAMETRIC_MODELING, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
})
|
||||
|
||||
const bracketNoNewLines = bracket.replace(/\n/g, '')
|
||||
|
||||
// Check the code got reset on load
|
||||
await expect(page.locator('#code-pane')).toBeVisible()
|
||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
// Mess with the code again
|
||||
await u.codeLocator.selectText()
|
||||
await u.codeLocator.fill(badCode)
|
||||
await expect(u.codeLocator).toHaveText(badCode)
|
||||
|
||||
// Click to the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
})
|
||||
|
||||
// Check that the code has been reset
|
||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
||||
})
|
||||
|
||||
test('Avatar text updates depending on image load success', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
// Test that the text in this step is correct
|
||||
const avatarLocator = await page
|
||||
.getByTestId('user-sidebar-toggle')
|
||||
.locator('img')
|
||||
const onboardingOverlayLocator = await page
|
||||
.getByTestId('onboarding-content')
|
||||
.locator('div')
|
||||
.nth(1)
|
||||
|
||||
// Expect the avatar to be visible and for the text to reference it
|
||||
await expect(avatarLocator).toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toContainText('your avatar')
|
||||
|
||||
// This is to force the avatar to 404.
|
||||
// For our test image (only triggers locally. on CI, it's Kurt's /
|
||||
// gravatar image )
|
||||
await page.route('/cat.jpg', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'text/plain',
|
||||
body: 'Not Found!',
|
||||
})
|
||||
})
|
||||
|
||||
// 404 the CI avatar image
|
||||
await page.route('https://lh3.googleusercontent.com/**', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'text/plain',
|
||||
body: 'Not Found!',
|
||||
})
|
||||
})
|
||||
|
||||
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||
|
||||
// Now expect the text to be different
|
||||
await expect(avatarLocator).not.toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
||||
})
|
||||
|
||||
test("Avatar text doesn't mention avatar when no avatar", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE')
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
// Test that the text in this step is correct
|
||||
const sidebar = page.getByTestId('user-sidebar-toggle')
|
||||
const avatar = sidebar.locator('img')
|
||||
const onboardingOverlayLocator = page
|
||||
.getByTestId('onboarding-content')
|
||||
.locator('div')
|
||||
.nth(1)
|
||||
|
||||
// Expect the avatar to be visible and for the text to reference it
|
||||
await expect(avatar).not.toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
||||
|
||||
// Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939
|
||||
// which doesn't deserver its own full test spun up
|
||||
const userMenuFeatures = [
|
||||
'manage your account',
|
||||
'report a bug',
|
||||
'request a feature',
|
||||
'sign out',
|
||||
]
|
||||
for (const feature of userMenuFeatures) {
|
||||
await expect(onboardingOverlayLocator).toContainText(feature)
|
||||
// Test that the onboarding pane is gone
|
||||
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||
await expect.poll(() => page.url()).not.toContain('/onboarding')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
test(
|
||||
'Onboarding redirects and code updating',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: '/export',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
const originalCode = 'sigmaAllow = 15000'
|
||||
|
||||
// Override beforeEach test setup
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
// Give some initial code, so we can test that it's cleared
|
||||
localStorage.setItem('persistCode', originalCode)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the redirect happened
|
||||
await expect.poll(() => page.url()).toContain('/onboarding/export')
|
||||
|
||||
// Test that you come back to this page when you refresh
|
||||
await page.reload()
|
||||
await expect.poll(() => page.url()).toContain('/onboarding/export')
|
||||
|
||||
// Test that the code changes when you advance to the next step
|
||||
await page.getByTestId('onboarding-next').hover()
|
||||
await page.getByTestId('onboarding-next').click()
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
const title = page.locator('[data-testid="onboarding-content"]')
|
||||
await expect(title).toBeAttached()
|
||||
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(originalCode)
|
||||
|
||||
// Test that the code is not empty when you click on the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').hover()
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Onboarding code gets reset to demo on Interactive Numbers step',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: '/parametric-modeling',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
|
||||
async ({ context, page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
const badCode = `// This is bad code we shouldn't see`
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 1080 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await expect
|
||||
.poll(() => page.url())
|
||||
.toContain(onboardingPaths.PARAMETRIC_MODELING)
|
||||
|
||||
const bracketNoNewLines = bracket.replace(/\n/g, '')
|
||||
|
||||
// Check the code got reset on load
|
||||
await expect(page.locator('#code-pane')).toBeVisible()
|
||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
// Mess with the code again
|
||||
await u.codeLocator.selectText()
|
||||
await u.codeLocator.fill(badCode)
|
||||
await expect(u.codeLocator).toHaveText(badCode)
|
||||
|
||||
// Click to the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').hover()
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
})
|
||||
|
||||
// Check that the code has been reset
|
||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
||||
}
|
||||
)
|
||||
|
||||
// (lee) The two avatar tests are weird because even on main, we don't have
|
||||
// anything to do with the avatar inside the onboarding test. Due to the
|
||||
// low impact of an avatar not showing I'm changing this to fixme.
|
||||
test.fixme(
|
||||
'Avatar text updates depending on image load success',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
// Override beforeEach test setup
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the text in this step is correct
|
||||
const avatarLocator = await page
|
||||
.getByTestId('user-sidebar-toggle')
|
||||
.locator('img')
|
||||
const onboardingOverlayLocator = await page
|
||||
.getByTestId('onboarding-content')
|
||||
.locator('div')
|
||||
.nth(1)
|
||||
|
||||
// Expect the avatar to be visible and for the text to reference it
|
||||
await expect(avatarLocator).toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toContainText('your avatar')
|
||||
|
||||
// This is to force the avatar to 404.
|
||||
// For our test image (only triggers locally. on CI, it's Kurt's /
|
||||
// gravatar image )
|
||||
await page.route('/cat.jpg', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'text/plain',
|
||||
body: 'Not Found!',
|
||||
})
|
||||
})
|
||||
|
||||
// 404 the CI avatar image
|
||||
await page.route(
|
||||
'https://lh3.googleusercontent.com/**',
|
||||
async (route) => {
|
||||
await route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'text/plain',
|
||||
body: 'Not Found!',
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||
|
||||
// Now expect the text to be different
|
||||
await expect(avatarLocator).not.toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme(
|
||||
"Avatar text doesn't mention avatar when no avatar",
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
// Override beforeEach test setup
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE')
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the text in this step is correct
|
||||
const sidebar = page.getByTestId('user-sidebar-toggle')
|
||||
const avatar = sidebar.locator('img')
|
||||
const onboardingOverlayLocator = page
|
||||
.getByTestId('onboarding-content')
|
||||
.locator('div')
|
||||
.nth(1)
|
||||
|
||||
// Expect the avatar to be visible and for the text to reference it
|
||||
await expect(avatar).not.toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toBeVisible()
|
||||
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
||||
|
||||
// Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939
|
||||
// which doesn't deserver its own full test spun up
|
||||
const userMenuFeatures = [
|
||||
'manage your account',
|
||||
'report a bug',
|
||||
'request a feature',
|
||||
'sign out',
|
||||
]
|
||||
for (const feature of userMenuFeatures) {
|
||||
await expect(onboardingOverlayLocator).toContainText(feature)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
'Restarting onboarding on desktop takes one attempt',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
)
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'dismissed',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
|
||||
// Our constants
|
||||
@ -418,9 +466,8 @@ test(
|
||||
const restartOnboardingButton = page.getByRole('button', {
|
||||
name: 'Reset onboarding',
|
||||
})
|
||||
const restartConfirmationButton = page.getByRole('button', {
|
||||
name: 'Make a new project',
|
||||
})
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
|
||||
const tutorialProjectIndicator = page
|
||||
.getByTestId('project-sidebar-toggle')
|
||||
.filter({ hasText: 'Tutorial Project 00' })
|
||||
@ -439,7 +486,7 @@ test(
|
||||
})
|
||||
|
||||
await test.step('Navigate into project', async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
@ -455,14 +502,22 @@ test(
|
||||
await helpMenuButton.click()
|
||||
await restartOnboardingButton.click()
|
||||
|
||||
await expect(restartConfirmationButton).toBeVisible()
|
||||
await restartConfirmationButton.click()
|
||||
await nextButton.hover()
|
||||
await nextButton.click()
|
||||
})
|
||||
|
||||
await test.step('Confirm that the onboarding has restarted', async () => {
|
||||
await expect(tutorialProjectIndicator).toBeVisible()
|
||||
await expect(tutorialModalText).toBeVisible()
|
||||
// Make sure the model loaded
|
||||
const XYPlanePoint = { x: 988, y: 523 } as const
|
||||
const modelColor: [number, number, number] = [76, 76, 76]
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||
await tutorialDismissButton.click()
|
||||
// Make sure model still there.
|
||||
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||
})
|
||||
|
||||
await test.step('Clear code and restart onboarding from settings', async () => {
|
||||
@ -480,11 +535,9 @@ test(
|
||||
|
||||
await restartOnboardingSettingsButton.click()
|
||||
// Since the code is empty, we should not see the confirmation dialog
|
||||
await expect(restartConfirmationButton).not.toBeVisible()
|
||||
await expect(nextButton).not.toBeVisible()
|
||||
await expect(tutorialProjectIndicator).toBeVisible()
|
||||
await expect(tutorialModalText).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
190
e2e/playwright/prompt-to-edit.spec.ts
Normal file
@ -0,0 +1,190 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
|
||||
/* eslint-disable jest/no-conditional-expect */
|
||||
|
||||
const file = `sketch001 = startSketchOn('XZ')
|
||||
profile001 = startProfileAt([57.81, 250.51], sketch001)
|
||||
|> line([121.13, 56.63], %, $seg02)
|
||||
|> line([83.37, -34.61], %, $seg01)
|
||||
|> line([19.66, -116.4], %)
|
||||
|> line([-221.8, -41.69], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(200, profile001)
|
||||
sketch002 = startSketchOn('XZ')
|
||||
|> startProfileAt([-73.64, -42.89], %)
|
||||
|> xLine(173.71, %)
|
||||
|> line([-22.12, -94.4], %)
|
||||
|> xLine(-156.98, %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude002 = extrude(50, sketch002)
|
||||
sketch003 = startSketchOn('XY')
|
||||
|> startProfileAt([52.92, 157.81], %)
|
||||
|> angledLine([0, 176.4], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
53.4
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude003 = extrude(20, sketch003)
|
||||
`
|
||||
|
||||
test.describe('Check the happy path, for basic changing color', () => {
|
||||
const cases = [
|
||||
{
|
||||
desc: 'User accepts change',
|
||||
shouldReject: false,
|
||||
},
|
||||
{
|
||||
desc: 'User rejects change',
|
||||
shouldReject: true,
|
||||
},
|
||||
] as const
|
||||
for (const { desc, shouldReject } of cases) {
|
||||
test(`${desc}`, async ({
|
||||
context,
|
||||
homePage,
|
||||
cmdBar,
|
||||
editor,
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
const body1CapCoords = { x: 571, y: 351 }
|
||||
const greenCheckCoords = { x: 565, y: 345 }
|
||||
const body2WallCoords = { x: 609, y: 153 }
|
||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||
body1CapCoords.x,
|
||||
body1CapCoords.y
|
||||
)
|
||||
const yellow: [number, number, number] = [179, 179, 131]
|
||||
const green: [number, number, number] = [108, 152, 75]
|
||||
const notGreen: [number, number, number] = [132, 132, 132]
|
||||
const body2NotGreen: [number, number, number] = [88, 88, 88]
|
||||
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
||||
const successToast = page.getByText('Prompt to edit successful')
|
||||
const acceptBtn = page.getByRole('button', { name: 'checkmark Accept' })
|
||||
const rejectBtn = page.getByRole('button', { name: 'close Reject' })
|
||||
|
||||
await test.step('wait for scene to load select body and check selection came through', async () => {
|
||||
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
||||
await clickBody1Cap()
|
||||
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
||||
diagnostics: [],
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('fire off edit prompt', async () => {
|
||||
await cmdBar.openCmdBar('promptToEdit')
|
||||
// being specific about the color with a hex means asserting pixel color is more stable
|
||||
await page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.fill('make this neon green please, use #39FF14')
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(submittingToast).toBeVisible()
|
||||
await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while
|
||||
await expect(successToast).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('verify initial change', async () => {
|
||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||
await scene.expectPixelColor(body2NotGreen, body2WallCoords, 15)
|
||||
await editor.expectEditor.toContain('appearance({')
|
||||
})
|
||||
|
||||
if (!shouldReject) {
|
||||
await test.step('check accept works and can be "undo"ed', async () => {
|
||||
await acceptBtn.click()
|
||||
await expect(successToast).not.toBeVisible()
|
||||
|
||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||
await editor.expectEditor.toContain('appearance({')
|
||||
|
||||
// ctrl-z works after accepting
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await editor.expectEditor.not.toContain('appearance({')
|
||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||
})
|
||||
} else {
|
||||
await test.step('check reject works', async () => {
|
||||
await rejectBtn.click()
|
||||
await expect(successToast).not.toBeVisible()
|
||||
|
||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||
await editor.expectEditor.not.toContain('appearance({')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test.describe('bad path', () => {
|
||||
test(`bad edit prompt`, async ({
|
||||
context,
|
||||
homePage,
|
||||
cmdBar,
|
||||
editor,
|
||||
toolbar,
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
const body1CapCoords = { x: 571, y: 351 }
|
||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||
body1CapCoords.x,
|
||||
body1CapCoords.y
|
||||
)
|
||||
const yellow: [number, number, number] = [179, 179, 131]
|
||||
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
||||
const failToast = page.getByText(
|
||||
'Failed to edit your KCL code, please try again with a different prompt or selection'
|
||||
)
|
||||
|
||||
await test.step('wait for scene to load and select body', async () => {
|
||||
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
||||
|
||||
await clickBody1Cap()
|
||||
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
||||
diagnostics: [],
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('fire of bad prompt', async () => {
|
||||
await cmdBar.openCmdBar('promptToEdit')
|
||||
await page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.fill('ansheusha asnthuatshoeuhtaoetuhthaeu laughs in dvorak')
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(submittingToast).toBeVisible()
|
||||
})
|
||||
await test.step('check fail toast appeared', async () => {
|
||||
await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while
|
||||
await expect(failToast).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
@ -1,46 +1,37 @@
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import { join } from 'path'
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import path from 'path'
|
||||
import * as fsp from 'fs/promises'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import { getUtils, executorInputPath } from './test-utils'
|
||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Regression tests', () => {
|
||||
// bugs we found that don't fit neatly into other categories
|
||||
test('bad model has inline error #3251', async ({ page }) => {
|
||||
test('bad model has inline error #3251', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
||||
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
||||
// Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch2 = startSketchOn("XY")
|
||||
sketch001 = startSketchAt([-0, -0])
|
||||
|> line([0, 0], %)
|
||||
|> line([-4.84, -5.29], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
sketch001 = startSketchAt([-0, -0])
|
||||
|> line([0, 0], %)
|
||||
|> line([-4.84, -5.29], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
@ -56,6 +47,7 @@ sketch001 = startSketchAt([-0, -0])
|
||||
})
|
||||
test('user should not have to press down twice in cmdbar', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
||||
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
||||
@ -64,26 +56,38 @@ sketch001 = startSketchAt([-0, -0])
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch2 = startSketchOn("XY")
|
||||
sketch001 = startSketchAt([-0, -0])
|
||||
|> line([0, 0], %)
|
||||
|> line([-4.84, -5.29], %)
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([82.33, 238.21], %)
|
||||
|> angledLine([0, 288.63], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
197.97
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
|> close(%)
|
||||
extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await page.goto('/')
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await test.step('Check arrow down works', async () => {
|
||||
await page.getByTestId('command-bar-open-button').hover()
|
||||
await page.getByTestId('command-bar-open-button').click()
|
||||
|
||||
await page
|
||||
.getByRole('option', { name: 'floppy disk arrow Export' })
|
||||
.click()
|
||||
const floppy = page.getByRole('option', {
|
||||
name: 'floppy disk arrow Export',
|
||||
})
|
||||
|
||||
await floppy.click()
|
||||
|
||||
// press arrow down key twice
|
||||
await page.keyboard.press('ArrowDown')
|
||||
@ -115,21 +119,22 @@ sketch001 = startSketchAt([-0, -0])
|
||||
)
|
||||
})
|
||||
})
|
||||
test('executes on load', async ({ page }) => {
|
||||
test('executes on load', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([-6.95, 4.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -14.93], %)
|
||||
|> line([-23.44, 0.52], %)`
|
||||
|> startProfileAt([-6.95, 4.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -14.93], %)
|
||||
|> line([-23.44, 0.52], %)`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// expand variables section
|
||||
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||
@ -148,14 +153,15 @@ sketch001 = startSketchAt([-0, -0])
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('re-executes', async ({ page }) => {
|
||||
test('re-executes', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', `myVar = 5`)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||
await variablesTabButton.click()
|
||||
@ -174,32 +180,33 @@ sketch001 = startSketchAt([-0, -0])
|
||||
page.locator('.pretty-json-container >> text=myVar:67')
|
||||
).toBeVisible()
|
||||
})
|
||||
test('ProgramMemory can be serialised', async ({ page }) => {
|
||||
test('ProgramMemory can be serialised', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 1], %)
|
||||
|> line([1, 0], %)
|
||||
|> line([0, -1], %)
|
||||
|> close(%)
|
||||
|> extrude(1, %)
|
||||
|> patternLinear3d({
|
||||
axis: [1, 0, 1],
|
||||
repetitions: 3,
|
||||
distance: 6
|
||||
}, %)`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 1], %)
|
||||
|> line([1, 0], %)
|
||||
|> line([0, -1], %)
|
||||
|> close(%)
|
||||
|> extrude(1, %)
|
||||
|> patternLinear3d({
|
||||
axis: [1, 0, 1],
|
||||
repetitions: 3,
|
||||
distance: 6
|
||||
}, %)`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
const messages: string[] = []
|
||||
|
||||
// Listen for all console events and push the message text to an array
|
||||
page.on('console', (message) => messages.push(message.text()))
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -212,19 +219,26 @@ sketch001 = startSketchAt([-0, -0])
|
||||
})
|
||||
})
|
||||
})
|
||||
test('ensure the Zoo logo is not a link in browser app', async ({ page }) => {
|
||||
|
||||
// Not relevant to us anymore, or at least for the time being.
|
||||
test.skip('ensure the Zoo logo is not a link in browser app', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const zooLogo = page.locator('[data-testid="app-logo"]')
|
||||
// Make sure it's not a link
|
||||
await expect(zooLogo).not.toHaveAttribute('href')
|
||||
})
|
||||
|
||||
test(
|
||||
'Position _ Is Out Of Range... regression test',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page }) => {
|
||||
async ({ context, page, homePage }) => {
|
||||
// SKip on windows, its being weird.
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
@ -233,25 +247,26 @@ sketch001 = startSketchAt([-0, -0])
|
||||
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.addInitScript(async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|>
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|>
|
||||
|
||||
example = extrude(5, exampleSketch)
|
||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||
example = extrude(5, exampleSketch)
|
||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||
)
|
||||
})
|
||||
|
||||
await expect(async () => {
|
||||
await page.goto('/')
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||
timeout: 1_000,
|
||||
@ -293,12 +308,12 @@ sketch001 = startSketchAt([-0, -0])
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|
||||
thing: "blah"`)
|
||||
thing: "blah"`)
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
}
|
||||
@ -306,6 +321,7 @@ sketch001 = startSketchAt([-0, -0])
|
||||
|
||||
test('when engine fails export we handle the failure and alert the user', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(
|
||||
@ -316,9 +332,10 @@ sketch001 = startSketchAt([-0, -0])
|
||||
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -374,7 +391,6 @@ sketch001 = startSketchAt([-0, -0])
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.clearCommandLogs()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
@ -408,7 +424,7 @@ sketch001 = startSketchAt([-0, -0])
|
||||
test(
|
||||
'ensure you can not export while an export is already going',
|
||||
{ tag: ['@skipLinux', '@skipWin'] },
|
||||
async ({ page }) => {
|
||||
async ({ page, homePage }) => {
|
||||
// This is being weird on ubuntu and windows.
|
||||
test.skip(
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
@ -428,9 +444,10 @@ sketch001 = startSketchAt([-0, -0])
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -500,20 +517,17 @@ sketch001 = startSketchAt([-0, -0])
|
||||
test(
|
||||
`Network health indicator only appears in modeling view`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Locators
|
||||
@ -539,18 +553,19 @@ sketch001 = startSketchAt([-0, -0])
|
||||
await u.waitForPageLoad()
|
||||
await expect(networkHealthIndicator).toContainText('Connected')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(`View gizmo stays visible even when zoomed out all the way`, async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// TODO: fix this test on windows after the electron migration
|
||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Constants and locators
|
||||
const planeColor: [number, number, number] = [161, 220, 155]
|
||||
const planeColor: [number, number, number] = [170, 220, 170]
|
||||
const bgColor: [number, number, number] = [27, 27, 27]
|
||||
const middlePixelIsColor = async (color: [number, number, number]) => {
|
||||
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
||||
@ -561,8 +576,9 @@ sketch001 = startSketchAt([-0, -0])
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', '')
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
@ -572,7 +588,7 @@ sketch001 = startSketchAt([-0, -0])
|
||||
timeout: 5000,
|
||||
message: 'Plane color is visible',
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
.toBeLessThanOrEqual(15)
|
||||
|
||||
let maxZoomOuts = 10
|
||||
let middlePixelIsBackgroundColor =
|
||||
@ -590,7 +606,7 @@ sketch001 = startSketchAt([-0, -0])
|
||||
}
|
||||
|
||||
expect(middlePixelIsBackgroundColor, {
|
||||
message: 'We no longer the default planes',
|
||||
message: 'We should not see the default planes',
|
||||
}).toBeTruthy()
|
||||
})
|
||||
|
||||
@ -598,6 +614,38 @@ sketch001 = startSketchAt([-0, -0])
|
||||
await expect(gizmo).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test(`Refreshing the app doesn't cause the stream to pause on long-executing files`, async ({
|
||||
context,
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
viewport,
|
||||
}) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const legoDir = path.join(dir, 'lego')
|
||||
await fsp.mkdir(legoDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('lego.kcl'),
|
||||
path.join(legoDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await homePage.openProject('lego')
|
||||
await toolbar.closePane('code')
|
||||
})
|
||||
await test.step(`Waiting for the loading spinner to disappear`, async () => {
|
||||
await scene.loadingIndicator.waitFor({ state: 'detached' })
|
||||
})
|
||||
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
|
||||
await scene.expectPixelColor(
|
||||
[143, 143, 143],
|
||||
{ x: (viewport?.width ?? 1200) / 2, y: (viewport?.height ?? 500) / 2 },
|
||||
15
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
async function clickExportButton(page: Page) {
|
||||
|
@ -47,7 +47,11 @@ test.beforeEach(async ({ page }) => {
|
||||
|
||||
test.setTimeout(60_000)
|
||||
|
||||
test(
|
||||
// We test this end to end already - getting this to work on web just to take
|
||||
// a snapshot of it feels weird. I'd rather our regular tests fail.
|
||||
// The primary failure is doExport now relies on the filesystem. We can follow
|
||||
// up with another PR if we want this back.
|
||||
test.skip(
|
||||
'exports of each format should work',
|
||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
||||
async ({ page, context }) => {
|
||||
@ -371,6 +375,7 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|
||||
await u.closeKclCodePanel()
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
await u.openKclCodePanel()
|
||||
}
|
||||
@ -950,7 +955,75 @@ test(
|
||||
|
||||
test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
// FIXME: Skip on macos its being weird.
|
||||
test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||
// test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||
|
||||
test('Grid turned off to on via command bar', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
const stream = page.getByTestId('stream')
|
||||
const mask = [
|
||||
page.locator('#app-header'),
|
||||
page.locator('#sidebar-top-ribbon'),
|
||||
page.locator('#sidebar-bottom-ribbon'),
|
||||
]
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
// wait for execution done
|
||||
await expect(
|
||||
page.locator('[data-message-type="execution-done"]')
|
||||
).toHaveCount(1)
|
||||
await u.closeDebugPanel()
|
||||
await u.closeKclCodePanel()
|
||||
// TODO: Find a way to truly know that the objects have finished
|
||||
// rendering, because an execution-done message is not sufficient.
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Open the command bar.
|
||||
await page
|
||||
.getByRole('button', { name: 'Commands', exact: false })
|
||||
.or(page.getByRole('button', { name: '⌘K' }))
|
||||
.click()
|
||||
const commandName = 'show scale grid'
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: commandName,
|
||||
exact: false,
|
||||
})
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
// This selector changes after we set the setting
|
||||
await cmdSearchBar.fill(commandName)
|
||||
await expect(commandOption).toBeVisible()
|
||||
await commandOption.click()
|
||||
|
||||
const toggleInput = page.getByPlaceholder('Off')
|
||||
await expect(toggleInput).toBeVisible()
|
||||
await expect(toggleInput).toBeFocused()
|
||||
|
||||
// Select On
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await expect(page.getByRole('option', { name: 'Off' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active selected'
|
||||
)
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await expect(page.getByRole('option', { name: 'On' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active'
|
||||
)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set show scale grid to "true" as a user default`)
|
||||
).toBeVisible()
|
||||
|
||||
await expect(stream).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask,
|
||||
})
|
||||
})
|
||||
|
||||
test('Grid turned off', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
@ -1096,3 +1169,109 @@ test.fixme('theme persists', async ({ page, context }) => {
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
||||
test('code color goober', async ({ page, context }) => {
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`// Create a pipe using a sweep.
|
||||
|
||||
// Create a path for the sweep.
|
||||
sweepPath = startSketchOn('XZ')
|
||||
|> startProfileAt([0.05, 0.05], %)
|
||||
|> line([0, 7], %)
|
||||
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
||||
|> line([-3, 0], %)
|
||||
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
||||
|> line([0, 7], %)
|
||||
|
||||
sweepSketch = startSketchOn('XY')
|
||||
|> startProfileAt([2, 0], %)
|
||||
|> arc({
|
||||
angleEnd = 360,
|
||||
angleStart = 0,
|
||||
radius = 2
|
||||
}, %)
|
||||
|> sweep({
|
||||
path = sweepPath,
|
||||
}, %)
|
||||
|> appearance({
|
||||
color = "#bb00ff",
|
||||
metalness = 90,
|
||||
roughness = 90
|
||||
}, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
await expect(page, 'expect small color widget').toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test('code color goober opening window', async ({ page, context }) => {
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`// Create a pipe using a sweep.
|
||||
|
||||
// Create a path for the sweep.
|
||||
sweepPath = startSketchOn('XZ')
|
||||
|> startProfileAt([0.05, 0.05], %)
|
||||
|> line([0, 7], %)
|
||||
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
||||
|> line([-3, 0], %)
|
||||
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
||||
|> line([0, 7], %)
|
||||
|
||||
sweepSketch = startSketchOn('XY')
|
||||
|> startProfileAt([2, 0], %)
|
||||
|> arc({
|
||||
angleEnd = 360,
|
||||
angleStart = 0,
|
||||
radius = 2
|
||||
}, %)
|
||||
|> sweep({
|
||||
path = sweepPath,
|
||||
}, %)
|
||||
|> appearance({
|
||||
color = "#bb00ff",
|
||||
metalness = 90,
|
||||
roughness = 90
|
||||
}, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()
|
||||
|
||||
// Click the color widget
|
||||
await page.locator('.cm-css-color-picker-wrapper input').click()
|
||||
|
||||
await expect(
|
||||
page,
|
||||
'expect small color widget to have window open'
|
||||
).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 44 KiB |