Compare commits
222 Commits
pierremtb/
...
nightly-v2
Author | SHA1 | Date | |
---|---|---|---|
cffeb52b4b | |||
e0ef10e7bb | |||
7095ce2377 | |||
5b207d7d1a | |||
2fac213c58 | |||
2f72a8ef14 | |||
27ce9f8aa4 | |||
b0426e3f94 | |||
d707c66e53 | |||
f8f44743fa | |||
f262eda12a | |||
9e1136195a | |||
4ff07ddaee | |||
1e565379a7 | |||
76e34ac4da | |||
4cd427bf91 | |||
f321ecdff0 | |||
d114ab798c | |||
69fec37107 | |||
8ca8c49cc3 | |||
b25fc302fd | |||
648b37c1dd | |||
18e5da5ca4 | |||
46524cda10 | |||
4585671a5d | |||
e7203b9e7a | |||
ab375f4b92 | |||
04ed6f52ee | |||
2332338ca1 | |||
41b97de3d1 | |||
8ef31a0be1 | |||
3adb42b5f2 | |||
20016b101e | |||
8d9dbf36c3 | |||
440704ed9f | |||
2261217a5d | |||
10da986649 | |||
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 |
@ -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."
|
||||
|
21
.github/dependabot.yml
vendored
@ -6,23 +6,36 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
directories:
|
||||
- '/'
|
||||
- '/packages/codemirror-lang-kcl/'
|
||||
- '/packages/codemirror-lsp-client/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
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'
|
||||
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'
|
||||
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
|
282
.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,194 +100,8 @@ jobs:
|
||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Install vector
|
||||
shell: bash
|
||||
if: ${{ !startsWith(matrix.os, 'windows') }}
|
||||
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 web
|
||||
run: yarn build:local
|
||||
shell: bash
|
||||
- name: Run ubuntu/chrome snapshots
|
||||
shell: bash
|
||||
run: |
|
||||
yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: development
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: playwright-report-${{ matrix.os }}-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- name: Clean up test-results
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
run: rm -r test-results
|
||||
- name: check for changes
|
||||
shell: bash
|
||||
id: git-check
|
||||
run: |
|
||||
git add .
|
||||
if git status | grep -q "Changes to be committed"
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
git add .
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
# only upload artifacts if there's actually changes
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
with:
|
||||
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
- uses: actions/download-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run playwright/chrome 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}}
|
||||
env:
|
||||
CI: true
|
||||
FAIL_ON_CONSOLE_ERRORS: true
|
||||
NODE_ENV: development
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- 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:
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
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
|
||||
# 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
|
||||
@ -313,44 +126,97 @@ jobs:
|
||||
- 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: |
|
||||
# PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
|
||||
# env:
|
||||
# CI: true
|
||||
# NODE_ENV: development
|
||||
# VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
# VITE_KC_SKIP_AUTH: true
|
||||
# token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
# snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: playwright-report-${{ matrix.os }}-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- name: Clean up test-results
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
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: |
|
||||
git add .
|
||||
if git status | grep -q "Changes to be committed"
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
# - name: Commit changes, if any
|
||||
# if: steps.git-check.outputs.modified == 'true'
|
||||
# shell: bash
|
||||
# run: |
|
||||
# git add .
|
||||
# git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
# git config --local user.name "github-actions[bot]"
|
||||
# git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||
# git fetch origin
|
||||
# echo ${{ github.head_ref }}
|
||||
# git checkout ${{ github.head_ref }}
|
||||
# git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
|
||||
# git push
|
||||
# git push origin ${{ github.head_ref }}
|
||||
# only upload artifacts if there's actually changes
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
with:
|
||||
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
- uses: actions/download-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run electron tests (with retries)
|
||||
- name: Run playwright/electron flow (with retries)
|
||||
id: retry
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
.github/ci-cd-scripts/playwright-electron.sh ${{ matrix.os }}
|
||||
.github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{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
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
|
2
.gitignore
vendored
@ -44,7 +44,7 @@ e2e/playwright/temp3.png
|
||||
e2e/playwright/export-snapshots/*
|
||||
!e2e/playwright/export-snapshots/*.png
|
||||
|
||||
|
||||
/kcl-samples
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
|
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
@ -4,14 +4,16 @@ excerpt: "Import a CAD file."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Import a CAD file.
|
||||
|
||||
**DEPRECATED** Prefer to use import statements.
|
||||
|
||||
For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.
|
||||
|
||||
Note: The import command currently only works when using the native Modeling App.
|
||||
|
||||
For importing KCL functions using the `import` statement, see the docs on [KCL modules](/docs/kcl/modules).
|
||||
|
||||
```js
|
||||
import(file_path: String, options?: ImportFormat) -> ImportedGeometry
|
||||
```
|
||||
|
@ -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,10 @@ 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 +80,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 +102,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().
|
||||
|
@ -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 */
|
||||
@ -79,7 +79,8 @@ 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,
|
||||
@ -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.
|
||||
|
||||
|
||||
|
41209
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')
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([-5, 10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)`
|
||||
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,18 +120,16 @@ 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 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)
|
||||
|
||||
@ -164,24 +153,25 @@ extrude001 = extrude(5, sketch001)`
|
||||
await expect(
|
||||
page
|
||||
.getByText(
|
||||
'sketch profile must lie entirely on one side of the revolution axis'
|
||||
'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,11 +231,9 @@ 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) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([
|
||||
@ -262,11 +250,10 @@ test(
|
||||
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 { 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,19 +1,13 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { test, expect } from './zoo-test'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { executorInputPath, 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)
|
||||
})
|
||||
import path from 'path'
|
||||
|
||||
test.describe('Command bar tests', () => {
|
||||
test('Extrude from command bar selects extrude line after', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -29,9 +23,9 @@ test.describe('Command bar tests', () => {
|
||||
})
|
||||
|
||||
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 +46,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 +108,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 +129,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,7 +176,7 @@ 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',
|
||||
@ -237,9 +192,9 @@ extrude001 = extrude(-10, sketch001)`
|
||||
})
|
||||
|
||||
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 +248,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' })
|
||||
@ -358,4 +306,132 @@ extrude001 = extrude(distance001, sketch001)`.replace(/(\r\n|\n|\r)/gm, '') // r
|
||||
await arcToolCommand.click()
|
||||
await expect(arcToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
|
||||
test(`Reacts to query param to open "import from URL" command`, async ({
|
||||
page,
|
||||
cmdBar,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
await test.step(`Prepare and navigate to home page with query params`, async () => {
|
||||
const targetURL = `?create-file&name=test&units=mm&code=ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D&ask-open-desktop`
|
||||
await homePage.expectState({
|
||||
projectCards: [],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await page.goto(page.url() + targetURL)
|
||||
expect(page.url()).toContain(targetURL)
|
||||
})
|
||||
|
||||
await test.step(`Submit the command`, async () => {
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Import file from URL',
|
||||
currentArgKey: 'method',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: '',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
highlightedHeaderArg: 'method',
|
||||
})
|
||||
await cmdBar.selectOption({ name: 'New Project' }).click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
commandName: 'Import file from URL',
|
||||
headerArguments: {
|
||||
Method: 'New project',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
|
||||
await editor.expectEditor.toContain('extrusionDistance = 12')
|
||||
})
|
||||
})
|
||||
|
||||
test(`"import from URL" can add to existing project`, async ({
|
||||
page,
|
||||
cmdBar,
|
||||
editor,
|
||||
homePage,
|
||||
toolbar,
|
||||
context,
|
||||
}) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const testProjectDir = path.join(dir, 'testProjectDir')
|
||||
await Promise.all([fsp.mkdir(testProjectDir, { recursive: true })])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
path.join(testProjectDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
})
|
||||
await test.step(`Prepare and navigate to home page with query params`, async () => {
|
||||
const targetURL = `?create-file&name=test&units=mm&code=ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D&ask-open-desktop`
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
fileCount: 1,
|
||||
title: 'testProjectDir',
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await page.goto(page.url() + targetURL)
|
||||
expect(page.url()).toContain(targetURL)
|
||||
})
|
||||
|
||||
await test.step(`Submit the command`, async () => {
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Import file from URL',
|
||||
currentArgKey: 'method',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: '',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
highlightedHeaderArg: 'method',
|
||||
})
|
||||
await cmdBar.selectOption({ name: 'Existing Project' }).click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Import file from URL',
|
||||
currentArgKey: 'projectName',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
Name: 'test',
|
||||
ProjectName: '',
|
||||
Code: '1 line',
|
||||
},
|
||||
highlightedHeaderArg: 'projectName',
|
||||
})
|
||||
await cmdBar.selectOption({ name: 'testProjectDir' }).click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
commandName: 'Import file from URL',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
ProjectName: 'testProjectDir',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
|
||||
await editor.expectEditor.toContain('extrusionDistance = 12')
|
||||
await toolbar.openPane('files')
|
||||
await toolbar.expectFileTreeState(['main.kcl', 'test.kcl'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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,15 +428,17 @@ 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 }) => {
|
||||
test('delete 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(``)
|
||||
@ -453,12 +463,15 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('backspace in code rejects the suggestion', async ({ page }) => {
|
||||
test('backspace 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(``)
|
||||
@ -483,12 +496,15 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('focus outside code pane rejects the suggestion', async ({ page }) => {
|
||||
test('focus outside code pane 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(``)
|
||||
@ -514,4 +530,5 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
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], %)
|
||||
`
|
||||
|> 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,20 +32,20 @@ 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.
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(code, { delay: 0 })
|
||||
// Scroll to the feature tree.
|
||||
// Scroll to the artifact graph.
|
||||
await tree.scrollIntoViewIfNeeded()
|
||||
// Expand the feature tree.
|
||||
await tree.getByText('Feature Tree').click()
|
||||
// Expand the artifact graph.
|
||||
await tree.getByText('Artifact Graph').click()
|
||||
// Just expanded the details, making the element taller, so scroll again.
|
||||
await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
|
||||
})
|
||||
// Extract the artifact IDs from the debug feature tree.
|
||||
// Extract the artifact IDs from the debug artifact graph.
|
||||
const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
|
||||
// The artifact ID should include a UUID.
|
||||
expect(initialSegmentIds).toMatch(
|
||||
|
@ -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')
|
||||
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'),
|
||||
join(bracketDir, 'other.kcl')
|
||||
path.join(bracketDir, 'other.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.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,26 +6,16 @@ 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()
|
||||
@ -64,13 +54,116 @@ test.describe('Editor tests', () => {
|
||||
|> 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()
|
||||
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('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()
|
||||
@ -96,11 +189,12 @@ test.describe('Editor tests', () => {
|
||||
|
||||
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()
|
||||
@ -151,9 +245,7 @@ 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], %)
|
||||
@ -171,9 +263,9 @@ test.describe('Editor tests', () => {
|
||||
|> 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,7 +316,10 @@ 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(
|
||||
@ -237,9 +332,9 @@ test.describe('Editor tests', () => {
|
||||
|> 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,6 +363,7 @@ 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 () => {
|
||||
@ -282,9 +378,9 @@ test.describe('Editor tests', () => {
|
||||
)
|
||||
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()
|
||||
@ -310,6 +406,7 @@ test.describe('Editor tests', () => {
|
||||
|
||||
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 () => {
|
||||
@ -324,9 +421,9 @@ test.describe('Editor tests', () => {
|
||||
)
|
||||
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"]')
|
||||
@ -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,7 +509,10 @@ 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(
|
||||
@ -423,9 +526,9 @@ test.describe('Editor tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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,10 +628,9 @@ 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,
|
||||
}) => {
|
||||
test.fixme(
|
||||
'error with 2 source ranges gets 2 diagnostics',
|
||||
async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -545,9 +652,11 @@ test.describe('Editor tests', () => {
|
||||
`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
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"]')
|
||||
@ -573,7 +682,7 @@ test.describe('Editor tests', () => {
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
||||
|> circle({ center = [0, 0], radius = dia/2 }, %)
|
||||
|> circle({ center: [0, 0], radius: dia/2 }, %)
|
||||
|> hole(squareHole(length, width, height), %)
|
||||
|> extrude(height, %)`)
|
||||
|
||||
@ -586,12 +695,14 @@ test.describe('Editor tests', () => {
|
||||
|
||||
// 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')
|
||||
@ -609,17 +720,16 @@ test.describe('Editor tests', () => {
|
||||
|> line([0, -10], %)
|
||||
|> close(%)
|
||||
|> revolve({
|
||||
axis = revolveAxis,
|
||||
angle = 90
|
||||
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
|
||||
@ -704,12 +817,12 @@ test.describe('Editor tests', () => {
|
||||
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.
|
||||
@ -775,9 +888,13 @@ test.describe('Editor tests', () => {
|
||||
|> 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')
|
||||
@ -788,9 +905,9 @@ test.describe('Editor tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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()
|
||||
@ -849,7 +966,10 @@ test.describe('Editor tests', () => {
|
||||
|> 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(
|
||||
@ -863,9 +983,9 @@ test.describe('Editor tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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], %)
|
||||
|> 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,8 +1080,8 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([15.39, -2.78], %)
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line([15.4, -2.78], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
@ -973,12 +1093,12 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> 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')
|
||||
@ -998,10 +1118,8 @@ test.describe('Editor tests', () => {
|
||||
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) => {
|
||||
async ({ page, context }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'cube')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
@ -1009,10 +1127,10 @@ test.describe('Editor tests', () => {
|
||||
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,10 +1,9 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
type CmdBarSerialised =
|
||||
| {
|
||||
stage: 'commandBarClosed'
|
||||
// TODO no more properties needed but needs to be implemented in _serialiseCmdBar
|
||||
}
|
||||
| {
|
||||
stage: 'pickCommand'
|
||||
@ -26,15 +25,20 @@ 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
|
||||
}
|
||||
|
||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
||||
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
|
||||
return { stage: 'commandBarClosed' }
|
||||
}
|
||||
const reviewForm = this.page.locator('#review-form')
|
||||
const getHeaderArgs = async () => {
|
||||
const inputs = await this.page.getByTestId('cmd-bar-input-tab').all()
|
||||
@ -116,4 +120,44 @@ 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()
|
||||
}
|
||||
|
||||
/**
|
||||
* Select an option from the command bar
|
||||
*/
|
||||
selectOption = (options: Parameters<typeof this.page.getByRole>[1]) => {
|
||||
return this.page.getByRole('option', options)
|
||||
}
|
||||
}
|
||||
|
@ -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,10 +221,34 @@ export class SceneFixture {
|
||||
coords: { x: number; y: number },
|
||||
diff: number
|
||||
) => {
|
||||
await expectPixelColor(this.page, colour, coords, diff)
|
||||
}
|
||||
|
||||
get gizmo() {
|
||||
return this.page.locator('[aria-label*=gizmo]')
|
||||
}
|
||||
|
||||
async clickGizmoMenuItem(name: string) {
|
||||
await this.gizmo.hover()
|
||||
await this.gizmo.click({ button: 'right' })
|
||||
const buttonToTest = this.page.getByRole('button', {
|
||||
name: name,
|
||||
})
|
||||
await expect(buttonToTest).toBeVisible()
|
||||
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(this.page)(coords, 1))[0]
|
||||
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
||||
if (!pixel) return null
|
||||
finalValue = pixel
|
||||
return pixel.every(
|
||||
@ -231,18 +262,4 @@ export class SceneFixture {
|
||||
{ cause }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
get gizmo() {
|
||||
return this.page.locator('[aria-label*=gizmo]')
|
||||
}
|
||||
|
||||
async clickGizmoMenuItem(name: string) {
|
||||
await this.gizmo.click({ button: 'right' })
|
||||
const buttonToTest = this.page.getByRole('button', {
|
||||
name: name,
|
||||
})
|
||||
await expect(buttonToTest).toBeVisible()
|
||||
await buttonToTest.click()
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,22 @@
|
||||
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
|
||||
@ -20,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
|
||||
@ -29,6 +43,9 @@ 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')
|
||||
@ -41,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)
|
||||
|
||||
@ -91,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) => {
|
||||
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) => {
|
||||
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)
|
||||
},
|
||||
{ settingsKey: TEST_SETTINGS_KEY }
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// 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')
|
||||
})
|
||||
|
||||
test(
|
||||
'Desktop: fresh onboarding executes and loads',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
'Onboarding code is shown in the editor',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
})
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
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()
|
||||
|
||||
// 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'
|
||||
)
|
||||
|
||||
// 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',
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
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,61 +91,80 @@ 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 }) => {
|
||||
test(
|
||||
'Code resets after confirmation',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
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) => {
|
||||
await context.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, initialCode)
|
||||
|
||||
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()
|
||||
|
||||
// Replay the onboarding
|
||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||
const replayButton = page.getByRole('button', { name: 'Replay onboarding' })
|
||||
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.getByText('Would you like to create')).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(initialCode)
|
||||
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
await expect(nextButton).toBeVisible()
|
||||
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')
|
||||
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)
|
||||
// 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.
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Click through each onboarding step',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
// Give no initial code, so that the onboarding start is shown immediately
|
||||
localStorage.setItem('persistCode', '')
|
||||
@ -154,107 +172,113 @@ test.describe('Onboarding tests', () => {
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }),
|
||||
settings: TOML.stringify({
|
||||
settings: TEST_SETTINGS_ONBOARDING_START,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
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()
|
||||
await expect(
|
||||
page.getByText('Welcome to Modeling App! This')
|
||||
).toBeVisible()
|
||||
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
|
||||
while ((await nextButton.innerText()) !== 'Finish') {
|
||||
await expect(nextButton).toBeVisible()
|
||||
await nextButton.hover()
|
||||
await nextButton.click()
|
||||
}
|
||||
|
||||
// Finish the onboarding
|
||||
await expect(nextButton).toBeVisible()
|
||||
await nextButton.hover()
|
||||
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)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }),
|
||||
await expect.poll(() => page.url()).not.toContain('/onboarding')
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
test(
|
||||
'Onboarding redirects and code updating',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: '/export',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
const originalCode = 'sigmaAllow = 15000'
|
||||
|
||||
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)
|
||||
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_PARAMETRIC_MODELING,
|
||||
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||
}),
|
||||
badCode,
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await page.waitForURL('**' + onboardingPaths.PARAMETRIC_MODELING, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
})
|
||||
// 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, '')
|
||||
|
||||
@ -270,6 +294,7 @@ test.describe('Onboarding tests', () => {
|
||||
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',
|
||||
@ -277,13 +302,25 @@ test.describe('Onboarding tests', () => {
|
||||
|
||||
// Check that the code has been reset
|
||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test('Avatar text updates depending on image load success', async ({
|
||||
page,
|
||||
}) => {
|
||||
// (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 page.addInitScript(
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
@ -295,11 +332,8 @@ test.describe('Onboarding tests', () => {
|
||||
}
|
||||
)
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the text in this step is correct
|
||||
const avatarLocator = await page
|
||||
@ -327,13 +361,16 @@ test.describe('Onboarding tests', () => {
|
||||
})
|
||||
|
||||
// 404 the CI avatar image
|
||||
await page.route('https://lh3.googleusercontent.com/**', async (route) => {
|
||||
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' })
|
||||
|
||||
@ -341,13 +378,22 @@ test.describe('Onboarding tests', () => {
|
||||
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,
|
||||
}) => {
|
||||
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 page.addInitScript(
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE')
|
||||
@ -360,11 +406,8 @@ test.describe('Onboarding tests', () => {
|
||||
}
|
||||
)
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
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')
|
||||
@ -390,23 +433,28 @@ test.describe('Onboarding tests', () => {
|
||||
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) => {
|
||||
{
|
||||
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,36 +1,26 @@
|
||||
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])
|
||||
sketch001 = startSketchAt([-0, -0])
|
||||
|> line([0, 0], %)
|
||||
|> line([-4.84, -5.29], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
@ -38,9 +28,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()
|
||||
|
||||
// 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,7 +119,7 @@ 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(
|
||||
@ -127,9 +131,10 @@ sketch001 = startSketchAt([-0, -0])
|
||||
|> 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,7 +180,7 @@ 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(
|
||||
@ -193,13 +199,14 @@ sketch001 = startSketchAt([-0, -0])
|
||||
}, %)`
|
||||
)
|
||||
})
|
||||
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,8 +247,8 @@ 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")
|
||||
@ -250,8 +264,9 @@ sketch001 = startSketchAt([-0, -0])
|
||||
})
|
||||
|
||||
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,
|
||||
@ -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')
|
||||
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'),
|
||||
join(bracketDir, 'main.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) {
|
||||
|
@ -1,27 +1,20 @@
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import { test as test2, expect as expect2 } from './fixtures/fixtureSetup'
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||
|
||||
import {
|
||||
getMovementUtils,
|
||||
getUtils,
|
||||
PERSIST_MODELING_CONTEXT,
|
||||
setup,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
import { uuidv4, roundOff } 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('Sketch tests', () => {
|
||||
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
|
||||
page,
|
||||
context,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
const selectionsSnippets = {
|
||||
@ -46,8 +39,8 @@ test.describe('Sketch tests', () => {
|
||||
${startProfileAt1}
|
||||
|> arc({
|
||||
radius = screwRadius,
|
||||
angle_start = 0,
|
||||
angle_end = 360
|
||||
angleStart = 0,
|
||||
angleEnd = 360
|
||||
}, %)
|
||||
|
||||
part001 = startSketchOn('XY')
|
||||
@ -67,8 +60,8 @@ test.describe('Sketch tests', () => {
|
||||
|> yLine(wireOffset, %)
|
||||
|> arc({
|
||||
radius = wireRadius,
|
||||
angle_start = 0,
|
||||
angle_end = 180
|
||||
angleStart = 0,
|
||||
angleEnd = 180
|
||||
}, %)
|
||||
|> yLine(-wireOffset, %)
|
||||
|> xLine(-width / 4, %)
|
||||
@ -79,9 +72,9 @@ test.describe('Sketch tests', () => {
|
||||
},
|
||||
selectionsSnippets
|
||||
)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -89,25 +82,24 @@ test.describe('Sketch tests', () => {
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.getByText(selectionsSnippets.startProfileAt1).click()
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
await page.getByText(selectionsSnippets.startProfileAt2).click()
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
await page.getByText(selectionsSnippets.startProfileAt3).click()
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
})
|
||||
test('Can delete most of a sketch and the line tool will still work', async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
@ -120,16 +112,14 @@ test.describe('Sketch tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
await expect(async () => {
|
||||
await page.mouse.click(700, 200)
|
||||
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeEnabled({ timeout: 1000 })
|
||||
).toBeEnabled({ timeout: 2000 })
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
|
||||
@ -142,8 +132,7 @@ test.describe('Sketch tests', () => {
|
||||
await page.keyboard.press('Home')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -151,26 +140,31 @@ test.describe('Sketch tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(async () => {
|
||||
await page.mouse.move(700, 200, { steps: 25 })
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
|> yLine(12.34, %)
|
||||
|
||||
`)
|
||||
await expect
|
||||
.poll(u.crushKclCodeIntoOneLineAndThenMaybeSome, { timeout: 1000 })
|
||||
.toBe(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61,-14.01], %)
|
||||
|> yLine(15.95, %)
|
||||
`
|
||||
.replaceAll(' ', '')
|
||||
.replaceAll('\n', '')
|
||||
)
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
})
|
||||
test('Can exit selection of face', async ({ page }) => {
|
||||
|
||||
test('Can exit selection of face', async ({ page, homePage }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', ``)
|
||||
})
|
||||
|
||||
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 page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await expect(
|
||||
@ -187,6 +181,7 @@ test.describe('Sketch tests', () => {
|
||||
test.describe('Can edit segments by dragging their handles', () => {
|
||||
const doEditSegmentsByDraggingHandle = async (
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
openPanes: string[]
|
||||
) => {
|
||||
// Load the app with the code panes
|
||||
@ -202,9 +197,8 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -318,7 +312,7 @@ test.describe('Sketch tests', () => {
|
||||
|> line([1.97, 2.06], %)
|
||||
|> close(%)`)
|
||||
}
|
||||
test('code pane open at start-handles', async ({ page }) => {
|
||||
test('code pane open at start-handles', async ({ page, homePage }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -331,10 +325,10 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
)
|
||||
})
|
||||
await doEditSegmentsByDraggingHandle(page, ['code'])
|
||||
await doEditSegmentsByDraggingHandle(page, homePage, ['code'])
|
||||
})
|
||||
|
||||
test('code pane closed at start-handles', async ({ page }) => {
|
||||
test('code pane closed at start-handles', async ({ page, homePage }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async (persistModelingContext) => {
|
||||
localStorage.setItem(
|
||||
@ -342,12 +336,14 @@ test.describe('Sketch tests', () => {
|
||||
JSON.stringify({ openPanes: [] })
|
||||
)
|
||||
}, PERSIST_MODELING_CONTEXT)
|
||||
await doEditSegmentsByDraggingHandle(page, [])
|
||||
await doEditSegmentsByDraggingHandle(page, homePage, [])
|
||||
})
|
||||
})
|
||||
|
||||
test('Can edit a circle center and radius by dragging its handles', async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
@ -358,9 +354,8 @@ test.describe('Sketch tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -399,6 +394,7 @@ test.describe('Sketch tests', () => {
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(1)
|
||||
@ -409,7 +405,9 @@ test.describe('Sketch tests', () => {
|
||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
|
||||
await editor.expectEditor.not.toContain(prevContent)
|
||||
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
})
|
||||
|
||||
@ -422,18 +420,20 @@ test.describe('Sketch tests', () => {
|
||||
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||
targetPosition: { x: lineEnd.x + dragPX * 2, y: lineEnd.y + dragPX },
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
await editor.expectEditor.not.toContain(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
})
|
||||
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [7.26, -2.37], radius = 11.44 }, %)
|
||||
`)
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [7.26, -2.37], radius = 11.44 }, %)`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
})
|
||||
test('Can edit a sketch that has been extruded in the same pipe', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
@ -448,9 +448,8 @@ test.describe('Sketch tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -504,11 +503,11 @@ test.describe('Sketch tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.waitForTimeout(100)
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
|
||||
sourcePosition: { x: lineEnd.x - 15, y: lineEnd.y },
|
||||
targetPosition: { x: lineEnd.x, y: lineEnd.y + 15 },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
@ -517,8 +516,8 @@ test.describe('Sketch tests', () => {
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
|
||||
targetPosition: {
|
||||
x: tangentEnd.x + dragPX,
|
||||
y: tangentEnd.y + dragPX,
|
||||
x: tangentEnd.x,
|
||||
y: tangentEnd.y - 15,
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
@ -528,15 +527,16 @@ test.describe('Sketch tests', () => {
|
||||
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], %)
|
||||
|> line([12.68, -1.09], %)
|
||||
|> tangentialArcTo([24.89, 0.68], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
`)
|
||||
})
|
||||
|
||||
test('Can edit a sketch that has been revolved in the same pipe', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
@ -551,9 +551,8 @@ test.describe('Sketch tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -636,12 +635,15 @@ test.describe('Sketch tests', () => {
|
||||
|> close(%)
|
||||
|> revolve({ axis = "X" }, %)`)
|
||||
})
|
||||
test('Can add multiple sketches', async ({ page }) => {
|
||||
test('Can add multiple sketches', 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)
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setBodyDimensions(viewportSize)
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||
@ -736,9 +738,8 @@ test.describe('Sketch tests', () => {
|
||||
scale = 1
|
||||
) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const code = `sketch001 = startSketchOn('-XZ')
|
||||
@ -820,17 +821,22 @@ test.describe('Sketch tests', () => {
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.removeCurrentCode()
|
||||
}
|
||||
test('[0, 100, 100]', async ({ page }) => {
|
||||
test('[0, 100, 100]', async ({ page, homePage }) => {
|
||||
await homePage.goToModelingScene()
|
||||
await doSnapAtDifferentScales(page, [0, 100, 100], 0.01)
|
||||
})
|
||||
|
||||
test('[0, 10000, 10000]', async ({ page }) => {
|
||||
test('[0, 10000, 10000]', async ({ page, homePage }) => {
|
||||
await homePage.goToModelingScene()
|
||||
await doSnapAtDifferentScales(page, [0, 10000, 10000])
|
||||
})
|
||||
})
|
||||
test('exiting a close extrude, has the extrude button enabled ready to go', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// TODO: fix this test on windows after the electron migration
|
||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -841,15 +847,15 @@ test.describe('Sketch tests', () => {
|
||||
|> line([1.02, -1.32], %, $seg01)
|
||||
|> line([-1.01, -0.77], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
|> 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()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -880,12 +886,15 @@ test.describe('Sketch tests', () => {
|
||||
// sketch selection should already have been made. "Selection: 1 face" only show up when the selection has been made already
|
||||
// otherwise the cmdbar would be waiting for a selection.
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'selection : 1 face', exact: false })
|
||||
page.getByRole('button', { name: 'selection : 1 segment', exact: false })
|
||||
).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
})
|
||||
test("Existing sketch with bad code delete user's code", async ({ page }) => {
|
||||
test("Existing sketch with bad code delete user's code", async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -897,15 +906,15 @@ test.describe('Sketch tests', () => {
|
||||
|> line([-1.01, -0.77], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)
|
||||
`
|
||||
extrude001 = extrude(5, sketch001)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
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"]')
|
||||
@ -937,124 +946,16 @@ extrude001 = extrude(5, sketch001)
|
||||
|> line([-1.01, -0.77], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)
|
||||
sketch002 = startSketchOn(extrude001, 'END')
|
||||
extrude001 = extrude(5, sketch001)
|
||||
sketch002 = startSketchOn(extrude001, 'END')
|
||||
|>
|
||||
`.replace(/\s/g, '')
|
||||
`.replace(/\s/g, '')
|
||||
)
|
||||
})
|
||||
|
||||
/* TODO: once we fix bug turn on.
|
||||
test('empty-scene default-planes act as expected when spaces in file', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||
const unHoveredColor: [number, number, number] = [47, 47, 93]
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
// Fill with spaces
|
||||
await u.codeLocator.fill(`
|
||||
`)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
})
|
||||
|
||||
test('empty-scene default-planes act as expected when only code comments in file', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||
const unHoveredColor: [number, number, number] = [47, 47, 93]
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
// Fill with spaces
|
||||
await u.codeLocator.fill(`// this is a code comments ya nerds
|
||||
`)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
})*/
|
||||
|
||||
test('empty-scene default-planes act as expected', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.skip(
|
||||
browserName === 'webkit',
|
||||
'Skip on Safari until `window.tearDown` is working there'
|
||||
)
|
||||
// TODO: fix after electron migration is merged
|
||||
test.fixme(
|
||||
'empty-scene default-planes act as expected',
|
||||
async ({ page, homePage }) => {
|
||||
/**
|
||||
* Tests the following things
|
||||
* 1) The the planes are there on load because the scene is empty
|
||||
@ -1067,9 +968,7 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
*/
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -1096,7 +995,7 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
`)
|
||||
`)
|
||||
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
@ -1117,7 +1016,7 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
|
||||
// click start Sketch
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 5 })
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 50 })
|
||||
const hoveredColor: [number, number, number] = [93, 93, 127]
|
||||
// now that we're expecting the user to select a plan, it does respond to hover
|
||||
await expect
|
||||
@ -1133,7 +1032,7 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([11.8, 9.09], %)
|
||||
|> line([3.39, -3.39], %)
|
||||
`)
|
||||
`)
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -1141,11 +1040,9 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([11.8, 9.09], %)
|
||||
|> line([3.39, -3.39], %)
|
||||
`
|
||||
`
|
||||
)
|
||||
})
|
||||
await page.reload()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -1155,18 +1052,12 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
|
||||
).toBeLessThan(3)
|
||||
})
|
||||
|
||||
test('Can attempt to sketch on revolved face', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.skip(
|
||||
browserName === 'webkit',
|
||||
'Skip on Safari until `window.tearDown` is working there'
|
||||
}
|
||||
)
|
||||
|
||||
test('Can attempt to sketch on revolved face', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -1191,7 +1082,7 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
)
|
||||
})
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -1223,9 +1114,10 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
|
||||
test('Can sketch on face when user defined function was used in the sketch', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
// Checking for a regression that performs a sketch when a user defined function
|
||||
// is declared at the top of the file and used in the sketch that is being drawn on.
|
||||
@ -1236,16 +1128,16 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
'persistCode',
|
||||
`fn in2mm = (inches) => {
|
||||
return inches * 25.4
|
||||
}
|
||||
}
|
||||
|
||||
const railTop = in2mm(.748)
|
||||
const railSide = in2mm(.024)
|
||||
const railBaseWidth = in2mm(.612)
|
||||
const railWideWidth = in2mm(.835)
|
||||
const railBaseLength = in2mm(.200)
|
||||
const railClampable = in2mm(.200)
|
||||
const railTop = in2mm(.748)
|
||||
const railSide = in2mm(.024)
|
||||
const railBaseWidth = in2mm(.612)
|
||||
const railWideWidth = in2mm(.835)
|
||||
const railBaseLength = in2mm(.200)
|
||||
const railClampable = in2mm(.200)
|
||||
|
||||
const rail = startSketchOn('XZ')
|
||||
const rail = startSketchOn('XZ')
|
||||
|> startProfileAt([
|
||||
-railTop / 2,
|
||||
railClampable + railBaseLength
|
||||
@ -1279,7 +1171,7 @@ const rail = startSketchOn('XZ')
|
||||
|
||||
const center = { x: 600, y: 250 }
|
||||
const rectangleSize = 20
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Start a sketch
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
@ -1318,27 +1210,33 @@ const rail = startSketchOn('XZ')
|
||||
})
|
||||
})
|
||||
|
||||
test2.describe('Sketch mode should be toleratant to syntax errors', () => {
|
||||
test2(
|
||||
test.describe('Sketch mode should be toleratant to syntax errors', () => {
|
||||
test(
|
||||
'adding a syntax error, recovers after fixing',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ app, scene, editor, toolbar }) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'a codemirror error appears in this test only on windows, that causes the test to fail only because of our "no new error" logic, but it can not be replicated locally'
|
||||
async ({ page, homePage, context, scene, editor, toolbar }) => {
|
||||
const file = await fs.readFile(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../../',
|
||||
'./src/wasm-lib/tests/executor/inputs/e2e-can-sketch-on-chamfer.kcl'
|
||||
),
|
||||
'utf-8'
|
||||
)
|
||||
const file = await app.getInputFile('e2e-can-sketch-on-chamfer.kcl')
|
||||
await app.initialise(file)
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
const [objClick] = scene.makeMouseHelpers(600, 250)
|
||||
const arrowHeadLocation = { x: 604, y: 129 } as const
|
||||
const arrowHeadLocation = { x: 706, y: 129 } as const
|
||||
const arrowHeadWhite: [number, number, number] = [255, 255, 255]
|
||||
const backgroundGray: [number, number, number] = [28, 28, 28]
|
||||
const verifyArrowHeadColor = async (c: [number, number, number]) =>
|
||||
scene.expectPixelColor(c, arrowHeadLocation, 15)
|
||||
|
||||
await test.step('check chamfer selection changes cursor positon', async () => {
|
||||
await expect2(async () => {
|
||||
await expect(async () => {
|
||||
// sometimes initial click doesn't register
|
||||
await objClick()
|
||||
await editor.expectActiveLinesToBe([
|
||||
@ -1374,24 +1272,36 @@ test2.describe('Sketch mode should be toleratant to syntax errors', () => {
|
||||
// this checks sketch segments have been drawn
|
||||
await verifyArrowHeadColor(arrowHeadWhite)
|
||||
})
|
||||
await app.page.waitForTimeout(100)
|
||||
await page.waitForTimeout(100)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test2.describe(`Sketching with offset planes`, () => {
|
||||
test2(
|
||||
`Can select an offset plane to sketch on`,
|
||||
async ({ app, scene, toolbar, editor }) => {
|
||||
test.describe(`Sketching with offset planes`, () => {
|
||||
test(`Can select an offset plane to sketch on`, async ({
|
||||
context,
|
||||
page,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
// We seed the scene with a single offset plane
|
||||
await app.initialise(`offsetPlane001 = offsetPlane("XY", 10)`)
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`offsetPlane001 = offsetPlane("XY", 10)`
|
||||
)
|
||||
})
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200)
|
||||
|
||||
await test2.step(`Start sketching on the offset plane`, async () => {
|
||||
await test.step(`Start sketching on the offset plane`, async () => {
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
|
||||
await test2.step(`Hovering should highlight code`, async () => {
|
||||
await test.step(`Hovering should highlight code`, async () => {
|
||||
await planeHover()
|
||||
await editor.expectState({
|
||||
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
|
||||
@ -1400,22 +1310,143 @@ test2.describe(`Sketching with offset planes`, () => {
|
||||
})
|
||||
})
|
||||
|
||||
await test2.step(
|
||||
`Clicking should select the plane and enter sketch mode`,
|
||||
async () => {
|
||||
await test.step(`Clicking should select the plane and enter sketch mode`, async () => {
|
||||
await planeClick()
|
||||
// Have to wait for engine-side animation to finish
|
||||
await app.page.waitForTimeout(600)
|
||||
await expect2(toolbar.lineBtn).toBeEnabled()
|
||||
await page.waitForTimeout(600)
|
||||
await expect(toolbar.lineBtn).toBeEnabled()
|
||||
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
|
||||
await editor.expectState({
|
||||
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
|
||||
diagnostics: [],
|
||||
highlightedCode: '',
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Regression test for https://github.com/KittyCAD/modeling-app/issues/4891
|
||||
test.describe(`Click based selection don't brick the app when clicked out of range after format using cache`, () => {
|
||||
test(`Can select a line that reformmed after entering sketch mode`, async ({
|
||||
context,
|
||||
page,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
// We seed the scene with a single offset plane
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([3.14, 3.14], %)
|
||||
|> arcTo({
|
||||
end = [4, 2],
|
||||
interior = [1, 2]
|
||||
}, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
await test.step(`format the code`, async () => {
|
||||
// doesn't contain condensed version
|
||||
await editor.expectEditor.not.toContain(
|
||||
`arcTo({ end = [4, 2], interior = [1, 2] }, %)`
|
||||
)
|
||||
// click the code to enter sketch mode
|
||||
await page.getByText(`arcTo`).click()
|
||||
// Format the code.
|
||||
await page.locator('#code-pane button:first-child').click()
|
||||
await page.locator('button:has-text("Format code")').click()
|
||||
})
|
||||
|
||||
await test.step(`Ensure the code reformatted`, async () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`arcTo({ end = [4, 2], interior = [1, 2] }, %)`
|
||||
)
|
||||
})
|
||||
|
||||
const [arcClick, arcHover] = scene.makeMouseHelpers(699, 337)
|
||||
await test.step('Ensure we can hover the arc', async () => {
|
||||
await arcHover()
|
||||
|
||||
// Check that the code is highlighted
|
||||
await editor.expectState({
|
||||
activeLines: ["sketch001=startSketchOn('XZ')"],
|
||||
diagnostics: [],
|
||||
highlightedCode: 'arcTo({end = [4, 2], interior = [1, 2]}, %)',
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('reset the selection', async () => {
|
||||
// Move the mouse out of the way
|
||||
await page.mouse.move(655, 337)
|
||||
|
||||
await editor.expectState({
|
||||
activeLines: ["sketch001=startSketchOn('XZ')"],
|
||||
diagnostics: [],
|
||||
highlightedCode: '',
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('Ensure we can click the arc', async () => {
|
||||
await arcClick()
|
||||
|
||||
// Check that the code is highlighted
|
||||
await editor.expectState({
|
||||
activeLines: [],
|
||||
diagnostics: [],
|
||||
highlightedCode: 'arcTo({end = [4, 2], interior = [1, 2]}, %)',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Regression test for https://github.com/KittyCAD/modeling-app/issues/4372
|
||||
test.describe('Redirecting to home page and back to the original file should clear sketch DOM elements', () => {
|
||||
test('Can redirect to home page and back to original file and have a cleared DOM', async ({
|
||||
context,
|
||||
page,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
// We seed the scene with a single offset plane
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
` sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([256.85, 14.41], %)
|
||||
|> lineTo([0, 211.07], %)
|
||||
`
|
||||
)
|
||||
})
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
const [objClick] = scene.makeMouseHelpers(634, 274)
|
||||
await objClick()
|
||||
|
||||
// Enter sketch mode
|
||||
await toolbar.editSketch()
|
||||
|
||||
await expect(page.getByText('323.49')).toBeVisible()
|
||||
|
||||
// Open navigation side bar
|
||||
await page.getByTestId('project-sidebar-toggle').click()
|
||||
const goToHome = page.getByRole('button', {
|
||||
name: 'Go to Home',
|
||||
})
|
||||
|
||||
await goToHome.click()
|
||||
await homePage.openProject('testDefault')
|
||||
await expect(page.getByText('323.49')).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
@ -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 |