Compare commits
232 Commits
pierremtb/
...
v0.39.0
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
668e2afb99 | |||
548c664db0 | |||
d3a3f4410c | |||
22eb343171 | |||
f2cfa4d5cf | |||
3f1f40eeba | |||
ff2d161606 | |||
210c78029d | |||
e27840219b | |||
c943a3f192 | |||
6aa588f09f | |||
59a6333aad | |||
403f1507ae | |||
eac7b83504 | |||
667500d1b9 | |||
b15aac9f48 | |||
54153aa646 | |||
943cf21d34 | |||
5a6728c45a |
@ -1,3 +1,3 @@
|
|||||||
[codespell]
|
[codespell]
|
||||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
|
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall,ser
|
||||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts
|
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": [
|
"plugins": [
|
||||||
"css-modules",
|
"css-modules",
|
||||||
|
"jest",
|
||||||
|
"jsx-a11y",
|
||||||
|
"react",
|
||||||
|
"react-hooks",
|
||||||
"suggest-no-throw",
|
"suggest-no-throw",
|
||||||
|
"testing-library",
|
||||||
|
"@typescript-eslint"
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
"plugin:css-modules/recommended",
|
||||||
"react-app/jest",
|
"plugin:jsx-a11y/recommended",
|
||||||
"plugin:css-modules/recommended"
|
"plugin:react-hooks/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
"@typescript-eslint/no-misused-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": [
|
"semi": [
|
||||||
"error",
|
"error",
|
||||||
"never"
|
"never"
|
||||||
@ -25,6 +41,9 @@
|
|||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
|
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
|
||||||
|
"extends": [
|
||||||
|
"plugin:testing-library/react"
|
||||||
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"suggest-no-throw/suggest-no-throw": "off",
|
"suggest-no-throw/suggest-no-throw": "off",
|
||||||
"testing-library/prefer-screen-queries": "off",
|
"testing-library/prefer-screen-queries": "off",
|
||||||
@ -33,6 +52,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": ["src/**/*.test.ts"],
|
"files": ["src/**/*.test.ts"],
|
||||||
|
"extends": [
|
||||||
|
"plugin:testing-library/react"
|
||||||
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"suggest-no-throw/suggest-no-throw": "off",
|
"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
|
# bash strict mode
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||||
# if no last run artifact, than run plawright normally
|
# if no last run artifact, than run plawright normally
|
||||||
echo "run playwright normally"
|
echo "run playwright normally"
|
||||||
if [[ "$1" == ubuntu-latest* ]]; then
|
if [[ "$3" == *ubuntu* ]]; then
|
||||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true
|
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --shard=$1/$2 || true
|
||||||
elif [[ "$1" == windows-latest* ]]; then
|
elif [[ "$3" == *windows* ]]; then
|
||||||
yarn test:playwright:electron:windows || true
|
yarn test:playwright:electron:windows -- --shard=$1/$2 || true
|
||||||
elif [[ "$1" == macos-14* ]]; then
|
elif [[ "$3" == *macos* ]]; then
|
||||||
yarn test:playwright:electron:macos || true
|
yarn test:playwright:electron:macos -- --shard=$1/$2 || true
|
||||||
else
|
else
|
||||||
echo "Do not run playwright. Unable to detect os runtime."
|
echo "Do not run playwright. Unable to detect os runtime."
|
||||||
exit 1
|
exit 1
|
||||||
@ -19,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
retry=1
|
retry=1
|
||||||
max_retrys=4
|
max_retrys=5
|
||||||
|
|
||||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||||
while [[ $retry -le $max_retrys ]]; do
|
while [[ $retry -le $max_retrys ]]; do
|
||||||
@ -28,11 +30,11 @@ while [[ $retry -le $max_retrys ]]; do
|
|||||||
if [[ $failed_tests -gt 0 ]]; then
|
if [[ $failed_tests -gt 0 ]]; then
|
||||||
echo "retried=true" >>$GITHUB_OUTPUT
|
echo "retried=true" >>$GITHUB_OUTPUT
|
||||||
echo "run playwright with last failed tests and retry $retry"
|
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
|
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
|
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
|
yarn test:playwright:electron:macos -- --last-failed || true
|
||||||
else
|
else
|
||||||
echo "Do not run playwright. Unable to detect os runtime."
|
echo "Do not run playwright. Unable to detect os runtime."
|
||||||
|
55
.github/dependabot.yml
vendored
@ -5,24 +5,37 @@
|
|||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: 'npm' # See documentation for possible values
|
- package-ecosystem: 'npm' # See documentation for possible values
|
||||||
directory: '/' # Location of package manifests
|
directories:
|
||||||
schedule:
|
- '/'
|
||||||
interval: 'weekly'
|
- '/packages/codemirror-lang-kcl/'
|
||||||
reviewers:
|
- '/packages/codemirror-lsp-client/'
|
||||||
- franknoirot
|
schedule:
|
||||||
- irev-dev
|
interval: weekly
|
||||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
day: monday
|
||||||
directory: '/' # Location of package manifests
|
reviewers:
|
||||||
schedule:
|
- franknoirot
|
||||||
interval: 'weekly'
|
- irev-dev
|
||||||
reviewers:
|
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||||
- adamchalmers
|
directory: '/' # Location of package manifests
|
||||||
- jessfraz
|
schedule:
|
||||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
interval: weekly
|
||||||
directory: '/src/wasm-lib/' # Location of package manifests
|
day: monday
|
||||||
schedule:
|
reviewers:
|
||||||
interval: 'weekly'
|
- adamchalmers
|
||||||
reviewers:
|
- jessfraz
|
||||||
- adamchalmers
|
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||||
- jessfraz
|
directory: '/src/wasm-lib/' # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
day: monday
|
||||||
|
reviewers:
|
||||||
|
- adamchalmers
|
||||||
|
- jessfraz
|
||||||
|
groups:
|
||||||
|
serde-dependencies:
|
||||||
|
patterns:
|
||||||
|
- "serde*"
|
||||||
|
wasm-bindgen-deps:
|
||||||
|
patterns:
|
||||||
|
- "wasm-bindgen*"
|
||||||
|
2
.github/workflows/build-and-store-wasm.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
# Upload the WASM bundle as an artifact
|
# Upload the WASM bundle as an artifact
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: wasm-bundle
|
name: wasm-bundle
|
||||||
path: src/wasm-lib/pkg
|
path: src/wasm-lib/pkg
|
||||||
|
27
.github/workflows/build-apps.yml
vendored
@ -126,7 +126,13 @@ jobs:
|
|||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
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
|
- run: yarn tronb:vite
|
||||||
|
|
||||||
@ -165,7 +171,6 @@ jobs:
|
|||||||
- name: Build the app (release)
|
- name: Build the app (release)
|
||||||
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
|
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
|
||||||
env:
|
env:
|
||||||
PUBLISH_FOR_PULL_REQUEST: true
|
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
@ -173,9 +178,14 @@ jobs:
|
|||||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
CSC_FOR_PULL_REQUEST: true
|
|
||||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
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/
|
- name: List artifacts in out/
|
||||||
run: ls -R out
|
run: ls -R out
|
||||||
@ -229,9 +239,14 @@ jobs:
|
|||||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
CSC_FOR_PULL_REQUEST: true
|
|
||||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
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
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ env.IS_RELEASE == 'true' }}
|
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:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- 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:
|
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:
|
workflow_dispatch:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
concurrency:
|
concurrency:
|
||||||
@ -71,7 +51,7 @@ jobs:
|
|||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
RUST_MIN_STACK: 10485760000
|
RUST_MIN_STACK: 10485760000
|
||||||
- name: Upload to codecov.io
|
- name: Upload to codecov.io
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{secrets.CODECOV_TOKEN}}
|
token: ${{secrets.CODECOV_TOKEN}}
|
||||||
fail_ci_if_error: true
|
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:
|
rust:
|
||||||
- 'src/wasm-lib/**'
|
- 'src/wasm-lib/**'
|
||||||
|
|
||||||
browser:
|
electron:
|
||||||
timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }}
|
timeout-minutes: 60
|
||||||
name: playwright:browser:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
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]
|
shardIndex: [1, 2, 3, 4]
|
||||||
shardTotal: [4]
|
shardTotal: [4]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
needs: check-rust-changes
|
needs: check-rust-changes
|
||||||
steps:
|
steps:
|
||||||
- name: Tune GitHub-hosted runner network
|
|
||||||
uses: smorimoto/tune-github-hosted-runner-network@v1
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@ -101,194 +100,8 @@ jobs:
|
|||||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||||
- name: Install vector
|
- name: Install vector
|
||||||
shell: bash
|
shell: bash
|
||||||
if: ${{ !startsWith(matrix.os, 'windows') }}
|
# TODO: figure out what to do with this, it's failing
|
||||||
run: |
|
if: false
|
||||||
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
|
|
||||||
run: |
|
run: |
|
||||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||||
chmod +x /tmp/vector.sh
|
chmod +x /tmp/vector.sh
|
||||||
@ -313,44 +126,97 @@ jobs:
|
|||||||
- name: build electron
|
- name: build electron
|
||||||
shell: bash
|
shell: bash
|
||||||
run: yarn tron:package
|
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
|
- uses: actions/download-artifact@v4
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
if: ${{ !cancelled() && (success() || failure()) }}
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
- name: Run electron tests (with retries)
|
- name: Run playwright/electron flow (with retries)
|
||||||
id: retry
|
id: retry
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
if: ${{ !cancelled() && (success() || failure()) }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
.github/ci-cd-scripts/playwright-electron.sh ${{ matrix.os }}
|
.github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
FAIL_ON_CONSOLE_ERRORS: true
|
FAIL_ON_CONSOLE_ERRORS: true
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
VITE_KC_SKIP_AUTH: true
|
VITE_KC_SKIP_AUTH: true
|
||||||
IS_UBUNTU: ${{ startsWith(matrix.os, 'ubuntu') && 'true' || 'false' }}
|
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
#DEBUG: 'pw:browser*'
|
|
||||||
- name: send to axiom
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) && !startsWith(matrix.os, 'windows') }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
node playwrightProcess.mjs | tee /tmp/github-actions.log
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }}
|
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
|
2
.gitignore
vendored
@ -44,7 +44,7 @@ e2e/playwright/temp3.png
|
|||||||
e2e/playwright/export-snapshots/*
|
e2e/playwright/export-snapshots/*
|
||||||
!e2e/playwright/export-snapshots/*.png
|
!e2e/playwright/export-snapshots/*.png
|
||||||
|
|
||||||
|
/kcl-samples
|
||||||
/test-results/
|
/test-results/
|
||||||
/playwright-report/
|
/playwright-report/
|
||||||
/blob-report/
|
/blob-report/
|
||||||
|
57
README.md
@ -337,13 +337,47 @@ For individual testing:
|
|||||||
yarn test abstractSyntaxTree -t "unexpected closed curly brace" --silent=false
|
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
|
### 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
|
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).
|
Where `XXX` is an API token from the production engine (NOT the dev environment).
|
||||||
@ -388,23 +422,6 @@ yarn test:unit:local
|
|||||||
|
|
||||||
#### E2E Tests
|
#### 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**
|
**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.
|
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
|
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||||
chamfer cases work currently.
|
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
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Import a CAD file.
|
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.
|
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.
|
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
|
```js
|
||||||
import(file_path: String, options?: ImportFormat) -> ImportedGeometry
|
import(file_path: String, options?: ImportFormat) -> ImportedGeometry
|
||||||
```
|
```
|
||||||
|
@ -19,6 +19,7 @@ layout: manual
|
|||||||
* [`angledLineThatIntersects`](kcl/angledLineThatIntersects)
|
* [`angledLineThatIntersects`](kcl/angledLineThatIntersects)
|
||||||
* [`angledLineToX`](kcl/angledLineToX)
|
* [`angledLineToX`](kcl/angledLineToX)
|
||||||
* [`angledLineToY`](kcl/angledLineToY)
|
* [`angledLineToY`](kcl/angledLineToY)
|
||||||
|
* [`appearance`](kcl/appearance)
|
||||||
* [`arc`](kcl/arc)
|
* [`arc`](kcl/arc)
|
||||||
* [`arcTo`](kcl/arcTo)
|
* [`arcTo`](kcl/arcTo)
|
||||||
* [`asin`](kcl/asin)
|
* [`asin`](kcl/asin)
|
||||||
@ -29,10 +30,12 @@ layout: manual
|
|||||||
* [`assertLessThan`](kcl/assertLessThan)
|
* [`assertLessThan`](kcl/assertLessThan)
|
||||||
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
||||||
* [`atan`](kcl/atan)
|
* [`atan`](kcl/atan)
|
||||||
|
* [`atan2`](kcl/atan2)
|
||||||
* [`bezierCurve`](kcl/bezierCurve)
|
* [`bezierCurve`](kcl/bezierCurve)
|
||||||
* [`ceil`](kcl/ceil)
|
* [`ceil`](kcl/ceil)
|
||||||
* [`chamfer`](kcl/chamfer)
|
* [`chamfer`](kcl/chamfer)
|
||||||
* [`circle`](kcl/circle)
|
* [`circle`](kcl/circle)
|
||||||
|
* [`circleThreePoint`](kcl/circleThreePoint)
|
||||||
* [`close`](kcl/close)
|
* [`close`](kcl/close)
|
||||||
* [`cm`](kcl/cm)
|
* [`cm`](kcl/cm)
|
||||||
* [`cos`](kcl/cos)
|
* [`cos`](kcl/cos)
|
||||||
@ -45,11 +48,10 @@ layout: manual
|
|||||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||||
* [`helix`](kcl/helix)
|
* [`helix`](kcl/helix)
|
||||||
|
* [`helixRevolutions`](kcl/helixRevolutions)
|
||||||
* [`hole`](kcl/hole)
|
* [`hole`](kcl/hole)
|
||||||
* [`hollow`](kcl/hollow)
|
* [`hollow`](kcl/hollow)
|
||||||
* [`import`](kcl/import)
|
|
||||||
* [`inch`](kcl/inch)
|
* [`inch`](kcl/inch)
|
||||||
* [`int`](kcl/int)
|
|
||||||
* [`lastSegX`](kcl/lastSegX)
|
* [`lastSegX`](kcl/lastSegX)
|
||||||
* [`lastSegY`](kcl/lastSegY)
|
* [`lastSegY`](kcl/lastSegY)
|
||||||
* [`legAngX`](kcl/legAngX)
|
* [`legAngX`](kcl/legAngX)
|
||||||
@ -78,6 +80,7 @@ layout: manual
|
|||||||
* [`pi`](kcl/pi)
|
* [`pi`](kcl/pi)
|
||||||
* [`polar`](kcl/polar)
|
* [`polar`](kcl/polar)
|
||||||
* [`polygon`](kcl/polygon)
|
* [`polygon`](kcl/polygon)
|
||||||
|
* [`pop`](kcl/pop)
|
||||||
* [`pow`](kcl/pow)
|
* [`pow`](kcl/pow)
|
||||||
* [`profileStart`](kcl/profileStart)
|
* [`profileStart`](kcl/profileStart)
|
||||||
* [`profileStartX`](kcl/profileStartX)
|
* [`profileStartX`](kcl/profileStartX)
|
||||||
@ -99,8 +102,8 @@ layout: manual
|
|||||||
* [`sin`](kcl/sin)
|
* [`sin`](kcl/sin)
|
||||||
* [`sqrt`](kcl/sqrt)
|
* [`sqrt`](kcl/sqrt)
|
||||||
* [`startProfileAt`](kcl/startProfileAt)
|
* [`startProfileAt`](kcl/startProfileAt)
|
||||||
* [`startSketchAt`](kcl/startSketchAt)
|
|
||||||
* [`startSketchOn`](kcl/startSketchOn)
|
* [`startSketchOn`](kcl/startSketchOn)
|
||||||
|
* [`sweep`](kcl/sweep)
|
||||||
* [`tan`](kcl/tan)
|
* [`tan`](kcl/tan)
|
||||||
* [`tangentToEnd`](kcl/tangentToEnd)
|
* [`tangentToEnd`](kcl/tangentToEnd)
|
||||||
* [`tangentialArc`](kcl/tangentialArc)
|
* [`tangentialArc`](kcl/tangentialArc)
|
||||||
|
@ -4,6 +4,8 @@ excerpt: "Convert a number to an integer."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Convert a number to an integer.
|
Convert a number to an integer.
|
||||||
|
|
||||||
DEPRECATED use floor(), ceil(), or round().
|
DEPRECATED use floor(), ceil(), or round().
|
||||||
|
@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
|
|||||||
```js
|
```js
|
||||||
r = 10 // radius
|
r = 10 // radius
|
||||||
// Call `map`, using an anonymous function instead of a named one.
|
// Call `map`, using an anonymous function instead of a named one.
|
||||||
circles = map([1..3], (id) {
|
circles = map([1..3], fn(id) {
|
||||||
return startSketchOn("XY")
|
return startSketchOn("XY")
|
||||||
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
||||||
})
|
})
|
||||||
|
@ -35,7 +35,7 @@ The transform function returns a transform object. All properties of the object
|
|||||||
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
|
- `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
|
```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 |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `total_instances` | `u32` | | Yes |
|
| `total_instances` | `integer` | | Yes |
|
||||||
| `transform_function` | `FunctionParam` | | Yes |
|
| `transform_function` | `FunctionParam` | | Yes |
|
||||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | 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]
|
p2 = [l + x, l + y]
|
||||||
p3 = [l + x, -l + y]
|
p3 = [l + x, -l + y]
|
||||||
|
|
||||||
return startSketchAt(p0)
|
return startSketchOn('XY')
|
||||||
|
|> startProfileAt(p0, %)
|
||||||
|> lineTo(p1, %)
|
|> lineTo(p1, %)
|
||||||
|> lineTo(p2, %)
|
|> lineTo(p2, %)
|
||||||
|> lineTo(p3, %)
|
|> lineTo(p3, %)
|
||||||
@ -132,7 +133,8 @@ fn cube(length, center) {
|
|||||||
p2 = [l + x, l + y]
|
p2 = [l + x, l + y]
|
||||||
p3 = [l + x, -l + y]
|
p3 = [l + x, -l + y]
|
||||||
|
|
||||||
return startSketchAt(p0)
|
return startSketchOn('XY')
|
||||||
|
|> startProfileAt(p0, %)
|
||||||
|> lineTo(p1, %)
|
|> lineTo(p1, %)
|
||||||
|> lineTo(p2, %)
|
|> lineTo(p2, %)
|
||||||
|> lineTo(p3, %)
|
|> lineTo(p3, %)
|
||||||
@ -195,7 +197,8 @@ fn transform(i) {
|
|||||||
{ rotation = { angle = 45 * i } }
|
{ rotation = { angle = 45 * i } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
startSketchAt([0, 0])
|
startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> polygon({
|
|> polygon({
|
||||||
radius = 10,
|
radius = 10,
|
||||||
numSides = 4,
|
numSides = 4,
|
||||||
|
@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
|
|||||||
|
|
||||||
|
|
||||||
```js
|
```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 |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `total_instances` | `u32` | | Yes |
|
| `total_instances` | `integer` | | Yes |
|
||||||
| `transform_function` | `FunctionParam` | | Yes |
|
| `transform_function` | `FunctionParam` | | Yes |
|
||||||
| `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | 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:
|
/* The above is basically like this pseudo-code:
|
||||||
fn sum(arr):
|
fn sum(arr):
|
||||||
let sumSoFar = 0
|
sumSoFar = 0
|
||||||
for i in arr:
|
for i in arr:
|
||||||
sumSoFar = add(sumSoFar, i)
|
sumSoFar = add(sumSoFar, i)
|
||||||
return sumSoFar */
|
return sumSoFar */
|
||||||
@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
|
|||||||
// an anonymous `add` function as its parameter, instead of declaring a
|
// an anonymous `add` function as its parameter, instead of declaring a
|
||||||
// named function outside.
|
// named function outside.
|
||||||
arr = [1, 2, 3]
|
arr = [1, 2, 3]
|
||||||
sum = reduce(arr, 0, (i, result_so_far) {
|
sum = reduce(arr, 0, fn(i, result_so_far) {
|
||||||
return i + result_so_far
|
return i + result_so_far
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -79,12 +79,13 @@ fn decagon(radius) {
|
|||||||
stepAngle = 1 / 10 * tau()
|
stepAngle = 1 / 10 * tau()
|
||||||
|
|
||||||
// Start the decagon sketch at this point.
|
// 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.
|
// Use a `reduce` to draw the remaining decagon sides.
|
||||||
// For each number in the array 1..10, run the given function,
|
// For each number in the array 1..10, run the given function,
|
||||||
// which takes a partially-sketched decagon and adds one more edge to it.
|
// which takes a partially-sketched decagon and adds one more edge to it.
|
||||||
fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {
|
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
|
||||||
// Draw one edge of the decagon.
|
// Draw one edge of the decagon.
|
||||||
x = cos(stepAngle * i) * radius
|
x = cos(stepAngle * i) * radius
|
||||||
y = sin(stepAngle * i) * radius
|
y = sin(stepAngle * i) * radius
|
||||||
@ -96,14 +97,15 @@ fn decagon(radius) {
|
|||||||
|
|
||||||
/* The `decagon` above is basically like this pseudo-code:
|
/* The `decagon` above is basically like this pseudo-code:
|
||||||
fn decagon(radius):
|
fn decagon(radius):
|
||||||
let stepAngle = (1/10) * tau()
|
stepAngle = (1/10) * tau()
|
||||||
let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
plane = startSketchOn('XY')
|
||||||
|
startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)
|
||||||
|
|
||||||
// Here's the reduce part.
|
// Here's the reduce part.
|
||||||
let partialDecagon = startOfDecagonSketch
|
partialDecagon = startOfDecagonSketch
|
||||||
for i in [1..10]:
|
for i in [1..10]:
|
||||||
let x = cos(stepAngle * i) * radius
|
x = cos(stepAngle * i) * radius
|
||||||
let y = sin(stepAngle * i) * radius
|
y = sin(stepAngle * i) * radius
|
||||||
partialDecagon = lineTo([x, y], partialDecagon)
|
partialDecagon = lineTo([x, y], partialDecagon)
|
||||||
fullDecagon = partialDecagon // it's now full
|
fullDecagon = partialDecagon // it's now full
|
||||||
return fullDecagon */
|
return fullDecagon */
|
||||||
|
@ -28,7 +28,8 @@ segEnd(tag: TagIdentifier) -> [number]
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
w = 15
|
w = 15
|
||||||
cube = startSketchAt([0, 0])
|
cube = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([w, 0], %, $line1)
|
|> line([w, 0], %, $line1)
|
||||||
|> line([0, w], %, $line2)
|
|> line([0, w], %, $line2)
|
||||||
|> line([-w, 0], %, $line3)
|
|> line([-w, 0], %, $line3)
|
||||||
@ -37,7 +38,8 @@ cube = startSketchAt([0, 0])
|
|||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|
|
||||||
fn cylinder(radius, tag) {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchAt([0, 0])
|
return startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> circle({
|
|> circle({
|
||||||
radius = radius,
|
radius = radius,
|
||||||
center = segEnd(tag)
|
center = segEnd(tag)
|
||||||
|
@ -28,7 +28,8 @@ segStart(tag: TagIdentifier) -> [number]
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
w = 15
|
w = 15
|
||||||
cube = startSketchAt([0, 0])
|
cube = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([w, 0], %, $line1)
|
|> line([w, 0], %, $line1)
|
||||||
|> line([0, w], %, $line2)
|
|> line([0, w], %, $line2)
|
||||||
|> line([-w, 0], %, $line3)
|
|> line([-w, 0], %, $line3)
|
||||||
@ -37,7 +38,8 @@ cube = startSketchAt([0, 0])
|
|||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|
|
||||||
fn cylinder(radius, tag) {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchAt([0, 0])
|
return startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> circle({
|
|> circle({
|
||||||
radius = radius,
|
radius = radius,
|
||||||
center = segStart(tag)
|
center = segStart(tag)
|
||||||
|
@ -4,6 +4,8 @@ excerpt: "Start a new 2-dimensional sketch at a given point on the 'XY' plane."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Start a new 2-dimensional sketch at a given point on the 'XY' plane.
|
Start a new 2-dimensional sketch at a given point on the 'XY' plane.
|
||||||
|
|
||||||
|
|
||||||
|
41187
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
|
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"
|
title: "Axis2dOrEdgeReference"
|
||||||
excerpt: "Axis or tagged edge."
|
excerpt: "A 2D axis or tagged edge."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Axis or tagged edge.
|
A 2D axis or tagged edge.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts any of the following:**
|
**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"
|
title: "AxisAndOrigin2d"
|
||||||
excerpt: "Axis and origin."
|
excerpt: "A 2D axis and origin."
|
||||||
layout: manual
|
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"
|
title: "HelixData"
|
||||||
excerpt: "Data for helices."
|
excerpt: "Data for a helix."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Data for helices.
|
Data for a helix.
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -19,6 +19,8 @@ Data for helices.
|
|||||||
| `revolutions` |`number`| Number of revolutions. | No |
|
| `revolutions` |`number`| Number of revolutions. | No |
|
||||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | 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`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -181,17 +180,10 @@ A plane.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
|
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
|
||||||
| `id` |`string`| The id of the plane. | No |
|
| `value` |[`Plane`](/docs/kcl/types/Plane)| Any KCL value. | 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 |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
A face.
|
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -203,14 +195,8 @@ A face.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Face`| | No |
|
| `type` |enum: [`Face`](/docs/kcl/types/Face)| | No |
|
||||||
| `id` |`string`| The id of the face. | No |
|
| `value` |[`Face`](/docs/kcl/types/Face)| Any KCL value. | 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 |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -246,7 +232,6 @@ A face.
|
|||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
An solid is a collection of extrude surfaces.
|
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -259,14 +244,7 @@ An solid is a collection of extrude surfaces.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
|
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
|
||||||
| `id` |`string`| The id of the solid. | No |
|
| `value` |[`Solid`](/docs/kcl/types/Solid)| Any KCL value. | 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 |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -285,6 +263,22 @@ An solid is a collection of extrude surfaces.
|
|||||||
| `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No |
|
| `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.
|
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
|
## Properties
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|
@ -16,6 +16,6 @@ Data for a mirror.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| 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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ Data for revolution surfaces.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `angle` |`number` (**maximum:** 360.0) (**minimum:** -360.0)| Angle to revolve (in degrees). Default is 360. | No |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | 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 |
|
| `__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 |
|
| `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 |
|
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | 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 |
|
| `__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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `__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 |
|
| `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 |
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | 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 |
|
| `__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 |
|
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
||||||
| `endCapId` |`string`| The id of the extrusion end 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 |
|
| `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 |
|
| `__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 |
|
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
||||||
| `endCapId` |`string`| The id of the extrusion end 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 |
|
| `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 |
|
| `__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 { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { setupElectron, tearDown } from './test-utils'
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Electron app header tests', () => {
|
test.describe('Electron app header tests', () => {
|
||||||
test(
|
test(
|
||||||
'Open Command Palette button has correct shortcut',
|
'Open Command Palette button has correct shortcut',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
testInfo,
|
|
||||||
folderSetupFn: async () => {},
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
// No space before the shortcut since it checks textContent.
|
// No space before the shortcut since it checks textContent.
|
||||||
let text
|
let text
|
||||||
@ -34,21 +23,14 @@ test.describe('Electron app header tests', () => {
|
|||||||
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
||||||
await expect(commandsButton).toBeVisible()
|
await expect(commandsButton).toBeVisible()
|
||||||
await expect(commandsButton).toHaveText(text)
|
await expect(commandsButton).toHaveText(text)
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'User settings has correct shortcut',
|
'User settings has correct shortcut',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
testInfo,
|
|
||||||
folderSetupFn: async () => {},
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
// Open the user sidebar menu.
|
// Open the user sidebar menu.
|
||||||
await page.getByTestId('user-sidebar-toggle').click()
|
await page.getByTestId('user-sidebar-toggle').click()
|
||||||
@ -59,8 +41,6 @@ test.describe('Electron app header tests', () => {
|
|||||||
const userSettingsButton = page.getByTestId('user-settings')
|
const userSettingsButton = page.getByTestId('user-settings')
|
||||||
await expect(userSettingsButton).toBeVisible()
|
await expect(userSettingsButton).toBeVisible()
|
||||||
await expect(userSettingsButton).toHaveText(text)
|
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 {
|
import {
|
||||||
getUtils,
|
getUtils,
|
||||||
TEST_COLORS,
|
TEST_COLORS,
|
||||||
setup,
|
|
||||||
tearDown,
|
|
||||||
commonPoints,
|
commonPoints,
|
||||||
PERSIST_MODELING_CONTEXT,
|
PERSIST_MODELING_CONTEXT,
|
||||||
} from './test-utils'
|
} from './test-utils'
|
||||||
|
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.setTimeout(120000)
|
test.setTimeout(120000)
|
||||||
|
|
||||||
async function doBasicSketch(page: Page, openPanes: string[]) {
|
async function doBasicSketch(
|
||||||
|
page: Page,
|
||||||
|
homePage: HomePageFixture,
|
||||||
|
openPanes: string[]
|
||||||
|
) {
|
||||||
const u = await getUtils(page)
|
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
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
// If we have the code pane open, we should see the code.
|
// 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.describe('Basic sketch', () => {
|
||||||
test('code pane open at start', { tag: ['@skipWin'] }, async ({ page }) => {
|
test.fixme('code pane open at start', async ({ page, homePage }) => {
|
||||||
// Skip on windows it is being weird.
|
await doBasicSketch(page, homePage, ['code'])
|
||||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
|
||||||
await doBasicSketch(page, ['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
|
// Load the app with the code panes
|
||||||
await page.addInitScript(async (persistModelingContext) => {
|
await page.addInitScript(async (persistModelingContext) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -162,6 +157,6 @@ test.describe('Basic sketch', () => {
|
|||||||
JSON.stringify({ openPanes: [] })
|
JSON.stringify({ openPanes: [] })
|
||||||
)
|
)
|
||||||
}, PERSIST_MODELING_CONTEXT)
|
}, PERSIST_MODELING_CONTEXT)
|
||||||
await doBasicSketch(page, [])
|
await doBasicSketch(page, homePage, [])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,27 +1,21 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect, Page } from './zoo-test'
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||||
|
import { getUtils } from './test-utils'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import { uuidv4 } from 'lib/utils'
|
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', () => {
|
test.describe('Can create sketches on all planes and their back sides', () => {
|
||||||
const sketchOnPlaneAndBackSideTest = async (
|
const sketchOnPlaneAndBackSideTest = async (
|
||||||
page: any,
|
page: Page,
|
||||||
|
homePage: HomePageFixture,
|
||||||
plane: string,
|
plane: string,
|
||||||
clickCoords: { x: number; y: number }
|
clickCoords: { x: number; y: number }
|
||||||
) => {
|
) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
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()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
const coord =
|
const coord =
|
||||||
@ -83,32 +77,39 @@ test.describe('Can create sketches on all planes and their back sides', () => {
|
|||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await u.removeCurrentCode()
|
await u.removeCurrentCode()
|
||||||
}
|
}
|
||||||
test('XY', async ({ page }) => {
|
test('XY', async ({ page, homePage }) => {
|
||||||
await sketchOnPlaneAndBackSideTest(
|
await sketchOnPlaneAndBackSideTest(
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
'XY',
|
'XY',
|
||||||
{ x: 600, y: 388 } // red plane
|
{ 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.
|
// { 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 }) => {
|
test('YZ', async ({ page, homePage }) => {
|
||||||
await sketchOnPlaneAndBackSideTest(page, 'YZ', { x: 700, y: 250 }) // green plane
|
await sketchOnPlaneAndBackSideTest(page, homePage, 'YZ', { x: 700, y: 250 }) // green plane
|
||||||
})
|
})
|
||||||
|
|
||||||
test('XZ', async ({ page }) => {
|
test('XZ', async ({ page, homePage }) => {
|
||||||
await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 80 }) // blue plane
|
await sketchOnPlaneAndBackSideTest(page, homePage, '-XZ', { x: 700, y: 80 }) // blue plane
|
||||||
})
|
})
|
||||||
|
|
||||||
test('-XY', async ({ page }) => {
|
test('-XY', async ({ page, homePage }) => {
|
||||||
await sketchOnPlaneAndBackSideTest(page, '-XY', { x: 600, y: 118 }) // back of red plane
|
await sketchOnPlaneAndBackSideTest(page, homePage, '-XY', {
|
||||||
|
x: 600,
|
||||||
|
y: 118,
|
||||||
|
}) // back of red plane
|
||||||
})
|
})
|
||||||
|
|
||||||
test('-YZ', async ({ page }) => {
|
test('-YZ', async ({ page, homePage }) => {
|
||||||
await sketchOnPlaneAndBackSideTest(page, '-YZ', { x: 700, y: 219 }) // back of green plane
|
await sketchOnPlaneAndBackSideTest(page, homePage, '-YZ', {
|
||||||
|
x: 700,
|
||||||
|
y: 219,
|
||||||
|
}) // back of green plan
|
||||||
})
|
})
|
||||||
|
|
||||||
test('-XZ', async ({ page }) => {
|
test('-XZ', async ({ page, homePage }) => {
|
||||||
await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 427 }) // back of blue plane
|
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 {
|
import { getUtils, executorInputPath } from './test-utils'
|
||||||
getUtils,
|
|
||||||
setup,
|
|
||||||
setupElectron,
|
|
||||||
tearDown,
|
|
||||||
executorInputPath,
|
|
||||||
} from './test-utils'
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
||||||
import fsp from 'fs/promises'
|
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.describe('Code pane and errors', () => {
|
||||||
test('Typing KCL errors induces a badge on the code pane button', async ({
|
test('Typing KCL errors induces a badge on the code pane button', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -31,18 +18,18 @@ test.describe('Code pane and errors', () => {
|
|||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`// Extruded Triangle
|
`// Extruded Triangle
|
||||||
sketch001 = startSketchOn('XZ')
|
sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([10, 0], %)
|
|> line([10, 0], %)
|
||||||
|> line([-5, 10], %)
|
|> line([-5, 10], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(5, sketch001)`
|
extrude001 = extrude(5, sketch001)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -62,11 +49,11 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
await expect(codePaneButtonHolder).toContainText('notification')
|
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,
|
page,
|
||||||
|
homePage,
|
||||||
|
editor,
|
||||||
}) => {
|
}) => {
|
||||||
await page.goto('http://localhost:3000')
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Load the app with the working starter code
|
// Load the app with the working starter code
|
||||||
@ -74,8 +61,8 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
}, bracket)
|
}, bracket)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 900 })
|
await page.setBodyDimensions({ width: 1200, height: 900 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -91,8 +78,9 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
await expect(codePaneButtonHolder).not.toContainText('notification')
|
await expect(codePaneButtonHolder).not.toContainText('notification')
|
||||||
|
|
||||||
// Delete a character to break the KCL
|
// Delete a character to break the KCL
|
||||||
await u.openKclCodePanel()
|
await editor.openPane()
|
||||||
await page.getByText('thickness, bracketLeg1Sketch)').click()
|
await editor.scrollToText('thickness, bracketLeg1Sketch)')
|
||||||
|
await page.getByText('extrude(thickness, bracketLeg1Sketch)').click()
|
||||||
await page.keyboard.press('Backspace')
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
// Ensure that a badge appears on the button
|
// 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()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
// Open the code pane
|
// 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
|
// Ensure that a badge appears on the button
|
||||||
await expect(codePaneButtonHolder).toContainText('notification')
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
@ -129,59 +120,58 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
await expect(page.locator('.cm-tooltip').first()).toBeVisible()
|
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 ({
|
test.fixme(
|
||||||
page,
|
'When error is not in view you can click the badge to scroll to it',
|
||||||
}) => {
|
async ({ page, homePage, context }) => {
|
||||||
const u = await getUtils(page)
|
// Load the app with the working starter code
|
||||||
|
await context.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||||
|
|
||||||
// Load the app with the working starter code
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await page.addInitScript((code) => {
|
await homePage.goToModelingScene()
|
||||||
localStorage.setItem('persistCode', code)
|
|
||||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.waitForTimeout(1000)
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await page.waitForTimeout(1000)
|
// Ensure badge is present
|
||||||
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
|
|
||||||
// Ensure badge is present
|
// Ensure we have no errors in the gutter, since error out of view.
|
||||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
await expect(codePaneButtonHolder).toContainText('notification')
|
|
||||||
|
|
||||||
// Ensure we have no errors in the gutter, since error out of view.
|
// Click the badge.
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
const badge = page.locator('#code-badge')
|
||||||
|
await expect(badge).toBeVisible()
|
||||||
|
await badge.click()
|
||||||
|
|
||||||
// Click the badge.
|
// Ensure we have an error diagnostic.
|
||||||
const badge = page.locator('#code-badge')
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
await expect(badge).toBeVisible()
|
|
||||||
await badge.click()
|
|
||||||
|
|
||||||
// Ensure we have an error diagnostic.
|
// Hover over the error to see the error message
|
||||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
await expect(
|
||||||
// Hover over the error to see the error message
|
page
|
||||||
await page.hover('.cm-lint-marker-error')
|
.getByText(
|
||||||
await expect(
|
'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
|
||||||
page
|
)
|
||||||
.getByText(
|
.first()
|
||||||
'sketch profile must lie entirely on one side of the revolution axis'
|
).toBeVisible()
|
||||||
)
|
}
|
||||||
.first()
|
)
|
||||||
).toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
|
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
|
||||||
|
context,
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
// Load the app with the working starter code
|
// Load the app with the working starter code
|
||||||
await page.addInitScript((code) => {
|
await context.addInitScript((code) => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
@ -241,32 +231,29 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
test(
|
test(
|
||||||
'Opening multiple panes persists when switching projects',
|
'Opening multiple panes persists when switching projects',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
// Setup multiple projects.
|
// Setup multiple projects.
|
||||||
const { electronApp, page } = await setupElectron({
|
await context.folderSetupFn(async (dir) => {
|
||||||
testInfo,
|
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||||
folderSetupFn: async (dir) => {
|
const bracketDir = join(dir, 'bracket')
|
||||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
await Promise.all([
|
||||||
const bracketDir = join(dir, 'bracket')
|
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||||
await Promise.all([
|
fsp.mkdir(bracketDir, { recursive: true }),
|
||||||
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
])
|
||||||
fsp.mkdir(bracketDir, { recursive: true }),
|
await Promise.all([
|
||||||
])
|
fsp.copyFile(
|
||||||
await Promise.all([
|
executorInputPath('router-template-slate.kcl'),
|
||||||
fsp.copyFile(
|
join(routerTemplateDir, 'main.kcl')
|
||||||
executorInputPath('router-template-slate.kcl'),
|
),
|
||||||
join(routerTemplateDir, 'main.kcl')
|
fsp.copyFile(
|
||||||
),
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
fsp.copyFile(
|
join(bracketDir, 'main.kcl')
|
||||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
),
|
||||||
join(bracketDir, 'main.kcl')
|
])
|
||||||
),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
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 test.step('Opening the bracket project should load', async () => {
|
||||||
await expect(page.getByText('bracket')).toBeVisible()
|
await expect(page.getByText('bracket')).toBeVisible()
|
||||||
@ -293,7 +280,7 @@ test(
|
|||||||
|
|
||||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||||
await expect(page.getByText('router-template-slate')).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 () => {
|
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('#variables-pane')).toBeVisible()
|
||||||
await expect(page.locator('#logs-pane')).toBeVisible()
|
await expect(page.locator('#logs-pane')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'external change of file contents are reflected in editor',
|
'external change of file contents are reflected in editor',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const PROJECT_DIR_NAME = 'lee-was-here'
|
const PROJECT_DIR_NAME = 'lee-was-here'
|
||||||
const {
|
const { dir: projectsDir } = await context.folderSetupFn(async (dir) => {
|
||||||
electronApp,
|
const aProjectDir = join(dir, PROJECT_DIR_NAME)
|
||||||
page,
|
await fsp.mkdir(aProjectDir, { recursive: true })
|
||||||
dir: projectsDir,
|
|
||||||
} = await setupElectron({
|
|
||||||
testInfo,
|
|
||||||
folderSetupFn: async (dir) => {
|
|
||||||
const aProjectDir = join(dir, PROJECT_DIR_NAME)
|
|
||||||
await fsp.mkdir(aProjectDir, { recursive: true })
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
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 test.step('Open the project', async () => {
|
||||||
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
|
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
|
||||||
@ -351,7 +329,5 @@ test(
|
|||||||
)
|
)
|
||||||
await u.editorTextMatches(content)
|
await u.editorTextMatches(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,37 +1,31 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
|
import * as fsp from 'fs/promises'
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
import { executorInputPath, getUtils } from './test-utils'
|
||||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
|
import path from 'path'
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Command bar tests', () => {
|
test.describe('Command bar tests', () => {
|
||||||
test('Extrude from command bar selects extrude line after', async ({
|
test('Extrude from command bar selects extrude line after', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XY')
|
`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> xLine(-20, %)
|
|> xLine(-20, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
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.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
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 ({
|
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await homePage.goToModelingScene()
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
@ -153,7 +108,7 @@ extrude001 = extrude(-10, sketch001)`
|
|||||||
// Check that the visibility changed
|
// Check that the visibility changed
|
||||||
await expect(paneSelector).not.toBeVisible()
|
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
|
// Test case for https://github.com/KittyCAD/modeling-app/issues/2882
|
||||||
await commandBarButton.click()
|
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 ({
|
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await homePage.goToModelingScene()
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -221,25 +176,25 @@ extrude001 = extrude(-10, sketch001)`
|
|||||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
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 () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`distance = sqrt(20)
|
`distance = sqrt(20)
|
||||||
sketch001 = startSketchOn('XZ')
|
sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-6.95, 10.98], %)
|
|> startProfileAt([-6.95, 10.98], %)
|
||||||
|> line([25.1, 0.41], %)
|
|> line([25.1, 0.41], %)
|
||||||
|> line([0.73, -20.93], %)
|
|> line([0.73, -20.93], %)
|
||||||
|> line([-23.44, 0.52], %)
|
|> line([-23.44, 0.52], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
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
|
// Make sure the stream is up
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -293,26 +248,19 @@ extrude001 = extrude(-10, sketch001)`
|
|||||||
await continueButton.click()
|
await continueButton.click()
|
||||||
await submitButton.click()
|
await submitButton.click()
|
||||||
|
|
||||||
// Check that the code was updated
|
|
||||||
await u.waitForCmdReceive('extrude')
|
await u.waitForCmdReceive('extrude')
|
||||||
// Unfortunately this indentation seems to matter for the test
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
`distance = sqrt(20)
|
'extrude001 = extrude(distance001, sketch001)'
|
||||||
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
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Can switch between sketch tools via command bar', async ({ page }) => {
|
test('Can switch between sketch tools via command bar', async ({
|
||||||
const u = await getUtils(page)
|
page,
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
homePage,
|
||||||
await u.waitForAuthSkipAppStart()
|
}) => {
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
||||||
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
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 arcToolCommand.click()
|
||||||
await expect(arcToolButton).toHaveAttribute('aria-pressed', 'true')
|
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 { test, expect } from './zoo-test'
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
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', () => {
|
test.describe('Copilot ghost text', () => {
|
||||||
// eslint-disable-next-line jest/valid-title
|
// eslint-disable-next-line jest/valid-title
|
||||||
test.skip(true, 'Needs to get covered again')
|
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 u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
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 ({
|
test.skip('copilot disabled in sketch mode no select plane', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
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 ({
|
test('copilot disabled in sketch mode after selecting plane', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
@ -184,12 +179,12 @@ test.describe('Copilot ghost text', () => {
|
|||||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
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 u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
@ -212,12 +207,15 @@ test.describe('Copilot ghost text', () => {
|
|||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
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 u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
@ -240,12 +238,15 @@ test.describe('Copilot ghost text', () => {
|
|||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
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 u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
@ -268,12 +269,15 @@ test.describe('Copilot ghost text', () => {
|
|||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
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 u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
@ -296,12 +300,12 @@ test.describe('Copilot ghost text', () => {
|
|||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
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 u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
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 u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
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 ({
|
test('Ctrl+z in code rejects the suggestion and undos the last code', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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 page.waitForTimeout(800)
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
@ -420,98 +428,107 @@ test.describe('Copilot ghost text', () => {
|
|||||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
// TODO when we make codemirror a widget, we can test this.
|
// 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 ({
|
||||||
const u = await getUtils(page)
|
page,
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
homePage,
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
)
|
)
|
||||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
`fn cube = (pos, scale) => {`
|
`fn cube = (pos, scale) => {`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Going elsewhere in the code should hide the ghost text.
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
await page.keyboard.press('Delete')
|
await page.keyboard.press('Delete')
|
||||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('backspace in code rejects the suggestion', async ({ page }) => {
|
test('backspace in code rejects the suggestion', async ({
|
||||||
const u = await getUtils(page)
|
page,
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
homePage,
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
)
|
)
|
||||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
`fn cube = (pos, scale) => {`
|
`fn cube = (pos, scale) => {`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Going elsewhere in the code should hide the ghost text.
|
// Going elsewhere in the code should hide the ghost text.
|
||||||
await page.keyboard.press('Backspace')
|
await page.keyboard.press('Backspace')
|
||||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('focus outside code pane rejects the suggestion', async ({ page }) => {
|
test('focus outside code pane rejects the suggestion', async ({
|
||||||
const u = await getUtils(page)
|
page,
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
homePage,
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
`fn cube = (pos, scale) => { sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||||
)
|
)
|
||||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||||
`fn cube = (pos, scale) => {`
|
`fn cube = (pos, scale) => {`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Going outside the editor should hide the ghost text.
|
// Going outside the editor should hide the ghost text.
|
||||||
await page.mouse.move(0, 0)
|
await page.mouse.move(0, 0)
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.waitFor({ state: 'visible' })
|
.waitFor({ state: 'visible' })
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
function countNewlines(input: string): number {
|
function countNewlines(input: string): number {
|
||||||
let count = 0
|
let count = 0
|
||||||
@ -24,13 +16,14 @@ test.describe('Debug pane', () => {
|
|||||||
test('Artifact IDs in the artifact graph are stable across code edits', async ({
|
test('Artifact IDs in the artifact graph are stable across code edits', async ({
|
||||||
page,
|
page,
|
||||||
context,
|
context,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const code = `sketch001 = startSketchOn('XZ')
|
const code = `sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([1, 1], %)
|
|> line([1, 1], %)
|
||||||
`
|
`
|
||||||
const u = await getUtils(page)
|
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 tree = page.getByTestId('debug-feature-tree')
|
||||||
const segment = tree.locator('li', {
|
const segment = tree.locator('li', {
|
||||||
@ -39,20 +32,20 @@ test.describe('Debug pane', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Test setup', async () => {
|
await test.step('Test setup', async () => {
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
// Set the code in the code editor.
|
// Set the code in the code editor.
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type(code, { delay: 0 })
|
await page.keyboard.type(code, { delay: 0 })
|
||||||
// Scroll to the feature tree.
|
// Scroll to the artifact graph.
|
||||||
await tree.scrollIntoViewIfNeeded()
|
await tree.scrollIntoViewIfNeeded()
|
||||||
// Expand the feature tree.
|
// Expand the artifact graph.
|
||||||
await tree.getByText('Feature Tree').click()
|
await tree.getByText('Artifact Graph').click()
|
||||||
// Just expanded the details, making the element taller, so scroll again.
|
// Just expanded the details, making the element taller, so scroll again.
|
||||||
await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
|
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 })
|
const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
|
||||||
// The artifact ID should include a UUID.
|
// The artifact ID should include a UUID.
|
||||||
expect(initialSegmentIds).toMatch(
|
expect(initialSegmentIds).toMatch(
|
||||||
|
@ -1,39 +1,31 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
import { join } from 'path'
|
import path from 'path'
|
||||||
import {
|
import {
|
||||||
getUtils,
|
getUtils,
|
||||||
setupElectron,
|
|
||||||
tearDown,
|
|
||||||
executorInputPath,
|
executorInputPath,
|
||||||
|
getPlaywrightDownloadDir,
|
||||||
} from './test-utils'
|
} from './test-utils'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'export works on the first try',
|
'export works on the first try',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ page, context }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await context.folderSetupFn(async (dir) => {
|
||||||
testInfo,
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
folderSetupFn: async (dir) => {
|
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||||
const bracketDir = join(dir, 'bracket')
|
await Promise.all([
|
||||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
fsp.copyFile(
|
||||||
await Promise.all([
|
executorInputPath('router-template-slate.kcl'),
|
||||||
fsp.copyFile(
|
path.join(bracketDir, 'other.kcl')
|
||||||
executorInputPath('router-template-slate.kcl'),
|
),
|
||||||
join(bracketDir, 'other.kcl')
|
fsp.copyFile(
|
||||||
),
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
fsp.copyFile(
|
path.join(bracketDir, 'main.kcl')
|
||||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
),
|
||||||
join(bracketDir, 'main.kcl')
|
])
|
||||||
),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
@ -93,12 +85,16 @@ test(
|
|||||||
await expect(successToastMessage).toBeVisible()
|
await expect(successToastMessage).toBeVisible()
|
||||||
await expect(exportingToastMessage).not.toBeVisible()
|
await expect(exportingToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
|
const firstFileFullPath = path.resolve(
|
||||||
|
getPlaywrightDownloadDir(page),
|
||||||
|
exportFileName
|
||||||
|
)
|
||||||
await test.step('Check the export size', async () => {
|
await test.step('Check the export size', async () => {
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
const outputGltf = await fsp.readFile(exportFileName)
|
const outputGltf = await fsp.readFile(firstFileFullPath)
|
||||||
return outputGltf.byteLength
|
return outputGltf.byteLength
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0
|
return 0
|
||||||
@ -107,9 +103,6 @@ test(
|
|||||||
{ timeout: 15_000 }
|
{ timeout: 15_000 }
|
||||||
)
|
)
|
||||||
.toBeGreaterThan(300_000)
|
.toBeGreaterThan(300_000)
|
||||||
|
|
||||||
// clean up exported file
|
|
||||||
await fsp.rm(exportFileName)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -170,12 +163,16 @@ test(
|
|||||||
expect(exportingToastMessage).not.toBeVisible(),
|
expect(exportingToastMessage).not.toBeVisible(),
|
||||||
]))
|
]))
|
||||||
|
|
||||||
|
const secondFileFullPath = path.resolve(
|
||||||
|
getPlaywrightDownloadDir(page),
|
||||||
|
exportFileName
|
||||||
|
)
|
||||||
await test.step('Check the export size', async () => {
|
await test.step('Check the export size', async () => {
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
const outputGltf = await fsp.readFile(exportFileName)
|
const outputGltf = await fsp.readFile(secondFileFullPath)
|
||||||
return outputGltf.byteLength
|
return outputGltf.byteLength
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0
|
return 0
|
||||||
@ -184,13 +181,7 @@ test(
|
|||||||
{ timeout: 15_000 }
|
{ timeout: 15_000 }
|
||||||
)
|
)
|
||||||
.toBeGreaterThan(100_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 fsp from 'fs/promises'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import {
|
import {
|
||||||
@ -6,37 +6,27 @@ import {
|
|||||||
darkModePlaneColorXZ,
|
darkModePlaneColorXZ,
|
||||||
executorInputPath,
|
executorInputPath,
|
||||||
getUtils,
|
getUtils,
|
||||||
setup,
|
|
||||||
setupElectron,
|
|
||||||
tearDown,
|
|
||||||
} from './test-utils'
|
} from './test-utils'
|
||||||
|
|
||||||
import { join } from 'path'
|
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.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)
|
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
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
|
||||||
await page.keyboard.down('ControlOrMeta')
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('/')
|
await page.keyboard.press('/')
|
||||||
@ -44,11 +34,11 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
// |> close(%)`)
|
// |> close(%)`)
|
||||||
|
|
||||||
// uncomment the code
|
// uncomment the code
|
||||||
await page.keyboard.down('ControlOrMeta')
|
await page.keyboard.down('ControlOrMeta')
|
||||||
@ -57,23 +47,22 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> 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,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(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()
|
||||||
// check no error to begin with
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
|
||||||
|
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||||
@ -82,36 +71,141 @@ test.describe('Editor tests', () => {
|
|||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
await page.locator('#code-pane button:first-child').click()
|
|
||||||
await page.locator('button:has-text("Format code")').click()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
// Ensure we execute the first time.
|
||||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
await u.openDebugPanel()
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(1)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(2)
|
||||||
|
|
||||||
|
// Add whitespace to the end of the code.
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('Home')
|
||||||
|
await page.keyboard.type(' ')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type(' ')
|
||||||
|
|
||||||
|
// Ensure we don't execute the second time.
|
||||||
|
await u.openDebugPanel()
|
||||||
|
// Make sure we didn't clear the scene.
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(3)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ensure we use the cache, and do not clear on append', async ({
|
||||||
|
homePage,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
// Ensure we execute the first time.
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(1)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(2)
|
||||||
|
|
||||||
|
// Add whitespace to the end of the code.
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('End')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type('const x = 1')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(3)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if you click the format button it formats your code', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
await page.locator('#code-pane button:first-child').click()
|
||||||
|
await page.locator('button:has-text("Format code")').click()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('if you click the format button it formats your code and executes so lints are still there', async ({
|
test('if you click the format button it formats your code and executes so lints are still there', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(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()
|
||||||
|
|
||||||
// check no error to begin with
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type(`sketch_001 = startSketchOn('XY')
|
await page.keyboard.type(`sketch_001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
@ -135,11 +229,11 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch_001 = startSketchOn('XY')
|
.toHaveText(`sketch_001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
|
||||||
// error in guter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
@ -151,29 +245,27 @@ test.describe('Editor tests', () => {
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('fold gutters work', async ({ page }) => {
|
test('fold gutters work', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
const fullCode = `sketch001 = startSketchOn('XY')
|
const fullCode = `sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`
|
|> close(%)`
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XY')
|
`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`
|
|> 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
|
// TODO: Jess needs to fix this but you have to mod the code to get them to show
|
||||||
// up, its an annoying codemirror thing.
|
// up, its an annoying codemirror thing.
|
||||||
@ -224,22 +316,25 @@ test.describe('Editor tests', () => {
|
|||||||
await expect(foldGutterFoldLine).not.toBeVisible()
|
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)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XY')
|
`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`
|
|> 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
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
@ -268,23 +363,24 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
test('if you use the format keyboard binding it formats your code', async ({
|
test('if you use the format keyboard binding it formats your code', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XY')
|
`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`
|
|> close(%)`
|
||||||
)
|
)
|
||||||
localStorage.setItem('disableAxis', 'true')
|
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
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
@ -301,32 +397,33 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('if you use the format keyboard binding it formats your code and executes so lints are shown', async ({
|
test('if you use the format keyboard binding it formats your code and executes so lints are shown', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch_001 = startSketchOn('XY')
|
`sketch_001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`
|
|> close(%)`
|
||||||
)
|
)
|
||||||
localStorage.setItem('disableAxis', 'true')
|
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.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
@ -353,11 +450,11 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch_001 = startSketchOn('XY')
|
.toHaveText(`sketch_001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
|
||||||
// error in guter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||||
@ -369,11 +466,14 @@ test.describe('Editor tests', () => {
|
|||||||
).toBeVisible()
|
).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)
|
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
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
||||||
@ -409,23 +509,26 @@ test.describe('Editor tests', () => {
|
|||||||
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
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)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([3.29, 7.86], %)
|
|> startProfileAt([3.29, 7.86], %)
|
||||||
|> line([2.48, 2.44], %)
|
|> line([2.48, 2.44], %)
|
||||||
|> line([2.66, 1.17], %)
|
|> line([2.66, 1.17], %)
|
||||||
|> close(%)
|
|> 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
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
@ -447,22 +550,27 @@ test.describe('Editor tests', () => {
|
|||||||
).not.toBeVisible()
|
).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)
|
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
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
/* add the following code to the editor ($ error is not a valid line)
|
/* add the following code to the editor (~ error is not a valid line)
|
||||||
$ error
|
* the old check here used $ but this is for tags so it changed meaning.
|
||||||
topAng = 30
|
* hopefully ~ doesn't change meaning
|
||||||
bottomAng = 25
|
~ error
|
||||||
*/
|
const topAng = 30
|
||||||
|
const bottomAng = 25
|
||||||
|
*/
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type('$ error')
|
await page.keyboard.type('~ error')
|
||||||
|
|
||||||
// press arrows to clear autocomplete
|
// press arrows to clear autocomplete
|
||||||
await page.keyboard.press('ArrowLeft')
|
await page.keyboard.press('ArrowLeft')
|
||||||
@ -474,17 +582,17 @@ test.describe('Editor tests', () => {
|
|||||||
await page.keyboard.type('bottomAng = 25')
|
await page.keyboard.type('bottomAng = 25')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// error in gutter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
|
||||||
// error text on hover
|
// error text on hover
|
||||||
await page.hover('.cm-lint-marker-error')
|
await page.hover('.cm-lint-marker-error')
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText('Tag names must not be empty').first()
|
page.getByText("found unknown token '~'").first()
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
// select the line that's causing the error and delete it
|
// 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.press('End')
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.keyboard.press('Home')
|
await page.keyboard.press('Home')
|
||||||
@ -520,106 +628,108 @@ test.describe('Editor tests', () => {
|
|||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO currently multiple source ranges are not supported
|
test.fixme(
|
||||||
test.skip('error with 2 source ranges gets 2 diagnostics', async ({
|
'error with 2 source ranges gets 2 diagnostics',
|
||||||
page,
|
async ({ page, homePage }) => {
|
||||||
}) => {
|
const u = await getUtils(page)
|
||||||
const u = await getUtils(page)
|
await page.addInitScript(async () => {
|
||||||
await page.addInitScript(async () => {
|
localStorage.setItem(
|
||||||
localStorage.setItem(
|
'persistCode',
|
||||||
'persistCode',
|
`length = .750
|
||||||
`length = .750
|
width = 0.500
|
||||||
width = 0.500
|
height = 0.500
|
||||||
height = 0.500
|
dia = 4
|
||||||
dia = 4
|
|
||||||
|
fn squareHole = (l, w) => {
|
||||||
|
squareHoleSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-width / 2, -length / 2], %)
|
||||||
|
|> lineTo([width / 2, -length / 2], %)
|
||||||
|
|> lineTo([width / 2, length / 2], %)
|
||||||
|
|> lineTo([-width / 2, length / 2], %)
|
||||||
|
|> close(%)
|
||||||
|
return squareHoleSketch
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
fn squareHole = (l, w) => {
|
await homePage.goToModelingScene()
|
||||||
squareHoleSketch = startSketchOn('XY')
|
await u.waitForPageLoad()
|
||||||
|> startProfileAt([-width / 2, -length / 2], %)
|
await page.waitForTimeout(1000)
|
||||||
|> lineTo([width / 2, -length / 2], %)
|
|
||||||
|> lineTo([width / 2, length / 2], %)
|
|
||||||
|> lineTo([-width / 2, length / 2], %)
|
|
||||||
|> close(%)
|
|
||||||
return squareHoleSketch
|
|
||||||
}
|
|
||||||
`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
// check no error to begin with
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
// check no error to begin with
|
// Click on the bottom of the code editor to add a new line
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
||||||
|
|> circle({ center: [0, 0], radius: dia/2 }, %)
|
||||||
|
|> hole(squareHole(length, width, height), %)
|
||||||
|
|> extrude(height, %)`)
|
||||||
|
|
||||||
// Click on the bottom of the code editor to add a new line
|
// error in gutter
|
||||||
await u.codeLocator.click()
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.hover('.cm-lint-marker-error:first-child')
|
||||||
await page.keyboard.press('ArrowDown')
|
await expect(
|
||||||
await page.keyboard.press('ArrowDown')
|
page.getByText('Expected 2 arguments, got 3').first()
|
||||||
await page.keyboard.press('ArrowDown')
|
).toBeVisible()
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
|
||||||
|> circle({ center = [0, 0], radius = dia/2 }, %)
|
|
||||||
|> hole(squareHole(length, width, height), %)
|
|
||||||
|> extrude(height, %)`)
|
|
||||||
|
|
||||||
// error in gutter
|
// Make sure there are two diagnostics
|
||||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
||||||
await page.hover('.cm-lint-marker-error:first-child')
|
}
|
||||||
await expect(
|
)
|
||||||
page.getByText('Expected 2 arguments, got 3').first()
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
// Make sure there are two diagnostics
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
|
||||||
})
|
|
||||||
test('if your kcl gets an error from the engine it is inlined', async ({
|
test('if your kcl gets an error from the engine it is inlined', async ({
|
||||||
|
context,
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
await context.addInitScript(async () => {
|
||||||
await page.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`box = startSketchOn('XY')
|
`box = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([0, 10], %)
|
|> line([0, 10], %)
|
||||||
|> line([10, 0], %)
|
|> line([10, 0], %)
|
||||||
|> line([0, -10], %, $revolveAxis)
|
|> line([0, -10], %, $revolveAxis)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(10, %)
|
|> extrude(10, %)
|
||||||
|
|
||||||
sketch001 = startSketchOn(box, revolveAxis)
|
sketch001 = startSketchOn(box, revolveAxis)
|
||||||
|> startProfileAt([5, 10], %)
|
|> startProfileAt([5, 10], %)
|
||||||
|> line([0, -10], %)
|
|> line([0, -10], %)
|
||||||
|> line([2, 0], %)
|
|> line([2, 0], %)
|
||||||
|> line([0, -10], %)
|
|> line([0, -10], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> revolve({
|
|> revolve({
|
||||||
axis = revolveAxis,
|
axis: revolveAxis,
|
||||||
angle = 90
|
angle: 90
|
||||||
}, %)
|
}, %)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await page.goto('/')
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
|
||||||
@ -630,12 +740,15 @@ test.describe('Editor tests', () => {
|
|||||||
await expect(page.getByText(searchText)).toBeVisible()
|
await expect(page.getByText(searchText)).toBeVisible()
|
||||||
})
|
})
|
||||||
test.describe('Autocomplete works', () => {
|
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 u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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
|
// tests clicking on an option, selection the first option
|
||||||
// and arrowing down to an option
|
// and arrowing down to an option
|
||||||
@ -697,19 +810,19 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([3.14, 12], %)
|
|> startProfileAt([3.14, 12], %)
|
||||||
|> xLine(5, %) // lin`)
|
|> xLine(5, %) // lin`)
|
||||||
|
|
||||||
// expect there to be no KCL errors
|
// expect there to be no KCL errors
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
|
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
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 u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// 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
|
// this test might be brittle as we add and remove functions
|
||||||
// but should also be easy to update.
|
// but should also be easy to update.
|
||||||
@ -771,26 +884,30 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([3.14, 12], %)
|
|> startProfileAt([3.14, 12], %)
|
||||||
|> xLine(5, %) // lin`)
|
|> 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)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([4.61, -14.01], %)
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|> line([12.73, -0.09], %)
|
|> line([12.73, -0.09], %)
|
||||||
|> tangentialArcTo([24.95, -5.38], %)
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|> close(%)`
|
|> close(%)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
@ -843,29 +960,32 @@ test.describe('Editor tests', () => {
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([4.61, -14.01], %)
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|> line([12.73, -0.09], %)
|
|> line([12.73, -0.09], %)
|
||||||
|> tangentialArcTo([24.95, -5.38], %)
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|> close(%)`)
|
|> 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)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([4.61, -10.01], %)
|
|> startProfileAt([4.61, -10.01], %)
|
||||||
|> line([12.73, -0.09], %)
|
|> line([12.73, -0.09], %)
|
||||||
|> tangentialArcTo([24.95, -0.38], %)
|
|> tangentialArcTo([24.95, -0.38], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)`
|
|> extrude(5, %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
@ -892,7 +1012,7 @@ test.describe('Editor tests', () => {
|
|||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const startPX = [665, 397]
|
const startPX = [1200 / 2, 500 / 2]
|
||||||
|
|
||||||
const dragPX = 40
|
const dragPX = 40
|
||||||
|
|
||||||
@ -906,9 +1026,9 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||||
|
|
||||||
// drag startProfieAt handle
|
// drag startProfileAt handle
|
||||||
await page.dragAndDrop('#stream', '#stream', {
|
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 },
|
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
@ -946,12 +1066,12 @@ test.describe('Editor tests', () => {
|
|||||||
// expect the code to have changed
|
// expect the code to have changed
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([7.12, -12.68], %)
|
|> startProfileAt([2.71, -2.71], %)
|
||||||
|> line([15.39, -2.78], %)
|
|> line([15.4, -2.78], %)
|
||||||
|> tangentialArcTo([27.6, -3.05], %)
|
|> tangentialArcTo([27.6, -3.05], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Hit undo
|
// Hit undo
|
||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
@ -960,11 +1080,11 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([7.12, -12.68], %)
|
|> startProfileAt([2.71, -2.71], %)
|
||||||
|> line([15.39, -2.78], %)
|
|> line([15.4, -2.78], %)
|
||||||
|> tangentialArcTo([24.95, -0.38], %)
|
|> tangentialArcTo([24.95, -0.38], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)`)
|
|> extrude(5, %)`)
|
||||||
|
|
||||||
// Hit undo again.
|
// Hit undo again.
|
||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
@ -973,12 +1093,12 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([7.12, -12.68], %)
|
|> startProfileAt([2.71, -2.71], %)
|
||||||
|> line([12.73, -0.09], %)
|
|> line([12.73, -0.09], %)
|
||||||
|> tangentialArcTo([24.95, -0.38], %)
|
|> tangentialArcTo([24.95, -0.38], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Hit undo again.
|
// Hit undo again.
|
||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
@ -988,31 +1108,29 @@ test.describe('Editor tests', () => {
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([4.61, -10.01], %)
|
|> startProfileAt([4.61, -10.01], %)
|
||||||
|> line([12.73, -0.09], %)
|
|> line([12.73, -0.09], %)
|
||||||
|> tangentialArcTo([24.95, -0.38], %)
|
|> tangentialArcTo([24.95, -0.38], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)`)
|
|> extrude(5, %)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.fixme(
|
test.fixme(
|
||||||
`Can use the import stdlib function on a local OBJ file`,
|
`Can use the import stdlib function on a local OBJ file`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ page, context }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await context.folderSetupFn(async (dir) => {
|
||||||
testInfo,
|
const bracketDir = join(dir, 'cube')
|
||||||
folderSetupFn: async (dir) => {
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
const bracketDir = join(dir, 'cube')
|
await fsp.copyFile(
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
executorInputPath('cube.obj'),
|
||||||
await fsp.copyFile(
|
join(bracketDir, 'cube.obj')
|
||||||
executorInputPath('cube.obj'),
|
)
|
||||||
join(bracketDir, 'cube.obj')
|
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
|
||||||
)
|
|
||||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
await page.setViewportSize(viewportSize)
|
await page.setBodyDimensions(viewportSize)
|
||||||
|
|
||||||
// Locators and constants
|
// Locators and constants
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -1070,8 +1188,6 @@ test.describe('Editor tests', () => {
|
|||||||
})
|
})
|
||||||
.toBeGreaterThan(15)
|
.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'
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
type CmdBarSerialised =
|
type CmdBarSerialised =
|
||||||
| {
|
| {
|
||||||
stage: 'commandBarClosed'
|
stage: 'commandBarClosed'
|
||||||
// TODO no more properties needed but needs to be implemented in _serialiseCmdBar
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
stage: 'pickCommand'
|
stage: 'pickCommand'
|
||||||
@ -26,15 +25,20 @@ type CmdBarSerialised =
|
|||||||
|
|
||||||
export class CmdBarFixture {
|
export class CmdBarFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
|
cmdBarOpenBtn!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
this.cmdBarOpenBtn = page.getByTestId('command-bar-open-button')
|
||||||
}
|
}
|
||||||
reConstruct = (page: Page) => {
|
reConstruct = (page: Page) => {
|
||||||
this.page = page
|
this.page = page
|
||||||
}
|
}
|
||||||
|
|
||||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
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 reviewForm = this.page.locator('#review-form')
|
||||||
const getHeaderArgs = async () => {
|
const getHeaderArgs = async () => {
|
||||||
const inputs = await this.page.getByTestId('cmd-bar-input-tab').all()
|
const inputs = await this.page.getByTestId('cmd-bar-input-tab').all()
|
||||||
@ -116,4 +120,44 @@ export class CmdBarFixture {
|
|||||||
await this.page.keyboard.press('Enter')
|
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 diagnosticsTooltip!: Locator
|
||||||
private diagnosticsGutterIcon!: Locator
|
private diagnosticsGutterIcon!: Locator
|
||||||
private codeContent!: Locator
|
private codeContent!: Locator
|
||||||
private activeLine!: Locator
|
public activeLine!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
@ -29,7 +29,7 @@ export class EditorFixture {
|
|||||||
reConstruct = (page: Page) => {
|
reConstruct = (page: Page) => {
|
||||||
this.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.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
||||||
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
|
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
|
||||||
this.activeLine = this.page.locator('.cm-activeLine')
|
this.activeLine = this.page.locator('.cm-activeLine')
|
||||||
@ -54,13 +54,13 @@ export class EditorFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!shouldNormalise) {
|
if (!shouldNormalise) {
|
||||||
const expectStart = expect(this.codeContent)
|
const expectStart = expect.poll(() => this.codeContent.textContent())
|
||||||
if (not) {
|
if (not) {
|
||||||
const result = await expectStart.not.toContainText(code, { timeout })
|
const result = await expectStart.not.toContain(code)
|
||||||
await resetPane()
|
await resetPane()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
const result = await expectStart.toContainText(code, { timeout })
|
const result = await expectStart.toContain(code)
|
||||||
await resetPane()
|
await resetPane()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -147,4 +147,28 @@ export class EditorFixture {
|
|||||||
openPane() {
|
openPane() {
|
||||||
return openPane(this.page, this.paneButtonTestId)
|
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 {
|
import type {
|
||||||
BrowserContext,
|
BrowserContext,
|
||||||
ElectronApplication,
|
ElectronApplication,
|
||||||
Page,
|
|
||||||
TestInfo,
|
TestInfo,
|
||||||
|
Page,
|
||||||
} from '@playwright/test'
|
} 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 fsp from 'fs/promises'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { CmdBarFixture } from './cmdBarFixture'
|
import { CmdBarFixture } from './cmdBarFixture'
|
||||||
@ -20,11 +20,13 @@ export class AuthenticatedApp {
|
|||||||
public readonly page: Page
|
public readonly page: Page
|
||||||
public readonly context: BrowserContext
|
public readonly context: BrowserContext
|
||||||
public readonly testInfo: TestInfo
|
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) {
|
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||||
this.page = page
|
|
||||||
this.context = context
|
this.context = context
|
||||||
|
this.page = page
|
||||||
this.testInfo = testInfo
|
this.testInfo = testInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,9 +51,7 @@ export class AuthenticatedApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Fixtures {
|
export interface Fixtures {
|
||||||
app: AuthenticatedApp
|
|
||||||
tronApp: AuthenticatedTronApp
|
|
||||||
cmdBar: CmdBarFixture
|
cmdBar: CmdBarFixture
|
||||||
editor: EditorFixture
|
editor: EditorFixture
|
||||||
toolbar: ToolbarFixture
|
toolbar: ToolbarFixture
|
||||||
@ -61,9 +61,11 @@ interface Fixtures {
|
|||||||
export class AuthenticatedTronApp {
|
export class AuthenticatedTronApp {
|
||||||
public readonly _page: Page
|
public readonly _page: Page
|
||||||
public page: Page
|
public page: Page
|
||||||
public readonly context: BrowserContext
|
public context: BrowserContext
|
||||||
public readonly testInfo: TestInfo
|
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) {
|
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||||
this._page = page
|
this._page = page
|
||||||
@ -79,15 +81,22 @@ export class AuthenticatedTronApp {
|
|||||||
appSettings?: Partial<SaveSettingsPayload>
|
appSettings?: Partial<SaveSettingsPayload>
|
||||||
} = { fixtures: {} }
|
} = { fixtures: {} }
|
||||||
) {
|
) {
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page, context, dir } = await setupElectron({
|
||||||
testInfo: this.testInfo,
|
testInfo: this.testInfo,
|
||||||
folderSetupFn: arg.folderSetupFn,
|
folderSetupFn: arg.folderSetupFn,
|
||||||
cleanProjectDir: arg.cleanProjectDir,
|
cleanProjectDir: arg.cleanProjectDir,
|
||||||
appSettings: arg.appSettings,
|
appSettings: arg.appSettings,
|
||||||
})
|
})
|
||||||
this.page = page
|
this.page = page
|
||||||
|
this.context = context
|
||||||
this.electronApp = electronApp
|
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)) {
|
for (const key of unsafeTypedKeys(arg.fixtures)) {
|
||||||
const fixture = arg.fixtures[key]
|
const fixture = arg.fixtures[key]
|
||||||
@ -110,32 +119,25 @@ export class AuthenticatedTronApp {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const test = base.extend<Fixtures>({
|
export const fixtures = {
|
||||||
app: async ({ page, context }, use, testInfo) => {
|
cmdBar: async ({ page }: { page: Page }, use: any) => {
|
||||||
await use(new AuthenticatedApp(context, page, testInfo))
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
},
|
|
||||||
tronApp: async ({ page, context }, use, testInfo) => {
|
|
||||||
await use(new AuthenticatedTronApp(context, page, testInfo))
|
|
||||||
},
|
|
||||||
cmdBar: async ({ page }, use) => {
|
|
||||||
await use(new CmdBarFixture(page))
|
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))
|
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))
|
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))
|
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))
|
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 {
|
export class HomePageFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
|
|
||||||
|
projectSection!: Locator
|
||||||
projectCard!: Locator
|
projectCard!: Locator
|
||||||
projectCardTitle!: Locator
|
projectCardTitle!: Locator
|
||||||
projectCardFile!: Locator
|
projectCardFile!: Locator
|
||||||
projectCardFolder!: Locator
|
projectCardFolder!: Locator
|
||||||
|
projectButtonNew!: Locator
|
||||||
|
projectButtonContinue!: Locator
|
||||||
|
projectTextName!: Locator
|
||||||
sortByDateBtn!: Locator
|
sortByDateBtn!: Locator
|
||||||
sortByNameBtn!: Locator
|
sortByNameBtn!: Locator
|
||||||
|
|
||||||
@ -28,11 +32,19 @@ export class HomePageFixture {
|
|||||||
reConstruct = (page: Page) => {
|
reConstruct = (page: Page) => {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
|
||||||
|
this.projectSection = this.page.getByTestId('home-section')
|
||||||
|
|
||||||
this.projectCard = this.page.getByTestId('project-link')
|
this.projectCard = this.page.getByTestId('project-link')
|
||||||
this.projectCardTitle = this.page.getByTestId('project-title')
|
this.projectCardTitle = this.page.getByTestId('project-title')
|
||||||
this.projectCardFile = this.page.getByTestId('project-file-count')
|
this.projectCardFile = this.page.getByTestId('project-file-count')
|
||||||
this.projectCardFolder = this.page.getByTestId('project-folder-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.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
||||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||||
}
|
}
|
||||||
@ -91,10 +103,25 @@ export class HomePageFixture {
|
|||||||
.toEqual(expectedState)
|
.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) => {
|
openProject = async (projectTitle: string) => {
|
||||||
const projectCard = this.projectCard.locator(
|
const projectCard = this.projectCard.locator(
|
||||||
this.page.getByText(projectTitle)
|
this.page.getByText(projectTitle)
|
||||||
)
|
)
|
||||||
await projectCard.click()
|
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 {
|
export class SceneFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
|
public streamWrapper!: Locator
|
||||||
|
public loadingIndicator!: Locator
|
||||||
private exeIndicator!: Locator
|
private exeIndicator!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
@ -53,8 +54,9 @@ export class SceneFixture {
|
|||||||
|
|
||||||
expectState = async (expected: SceneSerialised) => {
|
expectState = async (expected: SceneSerialised) => {
|
||||||
return expect
|
return expect
|
||||||
.poll(() => this._serialiseScene(), {
|
.poll(async () => await this._serialiseScene(), {
|
||||||
message: `Expected scene state to match`,
|
intervals: [1_000, 2_000, 10_000],
|
||||||
|
timeout: 60000,
|
||||||
})
|
})
|
||||||
.toEqual(expected)
|
.toEqual(expected)
|
||||||
}
|
}
|
||||||
@ -63,6 +65,8 @@ export class SceneFixture {
|
|||||||
this.page = page
|
this.page = page
|
||||||
|
|
||||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||||
|
this.streamWrapper = page.getByTestId('stream')
|
||||||
|
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
|
||||||
}
|
}
|
||||||
|
|
||||||
makeMouseHelpers = (
|
makeMouseHelpers = (
|
||||||
@ -187,7 +191,10 @@ export class SceneFixture {
|
|||||||
type: 'default_camera_get_settings',
|
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([
|
const position = await Promise.all([
|
||||||
this.page.getByTestId('cam-x-position').inputValue().then(Number),
|
this.page.getByTestId('cam-x-position').inputValue().then(Number),
|
||||||
this.page.getByTestId('cam-y-position').inputValue().then(Number),
|
this.page.getByTestId('cam-y-position').inputValue().then(Number),
|
||||||
@ -214,23 +221,7 @@ export class SceneFixture {
|
|||||||
coords: { x: number; y: number },
|
coords: { x: number; y: number },
|
||||||
diff: number
|
diff: number
|
||||||
) => {
|
) => {
|
||||||
let finalValue = colour
|
await expectPixelColor(this.page, colour, coords, diff)
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
const pixel = (await getPixelRGBs(this.page)(coords, 1))[0]
|
|
||||||
if (!pixel) return null
|
|
||||||
finalValue = pixel
|
|
||||||
return pixel.every(
|
|
||||||
(channel, index) => Math.abs(channel - colour[index]) < diff
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.toBeTruthy()
|
|
||||||
.catch((cause) => {
|
|
||||||
throw new Error(
|
|
||||||
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
|
||||||
{ cause }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get gizmo() {
|
get gizmo() {
|
||||||
@ -238,6 +229,7 @@ export class SceneFixture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async clickGizmoMenuItem(name: string) {
|
async clickGizmoMenuItem(name: string) {
|
||||||
|
await this.gizmo.hover()
|
||||||
await this.gizmo.click({ button: 'right' })
|
await this.gizmo.click({ button: 'right' })
|
||||||
const buttonToTest = this.page.getByRole('button', {
|
const buttonToTest = this.page.getByRole('button', {
|
||||||
name: name,
|
name: name,
|
||||||
@ -246,3 +238,28 @@ export class SceneFixture {
|
|||||||
await buttonToTest.click()
|
await buttonToTest.click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectPixelColor(
|
||||||
|
page: Page,
|
||||||
|
colour: [number, number, number],
|
||||||
|
coords: { x: number; y: number },
|
||||||
|
diff: number
|
||||||
|
) {
|
||||||
|
let finalValue = colour
|
||||||
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
|
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
||||||
|
if (!pixel) return null
|
||||||
|
finalValue = pixel
|
||||||
|
return pixel.every(
|
||||||
|
(channel, index) => Math.abs(channel - colour[index]) < diff
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.toBeTruthy()
|
||||||
|
.catch((cause) => {
|
||||||
|
throw new Error(
|
||||||
|
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
||||||
|
{ cause }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
import type { Page, Locator } from '@playwright/test'
|
import type { Page, Locator } from '@playwright/test'
|
||||||
import { expect } from './fixtureSetup'
|
import { expect } from '../zoo-test'
|
||||||
import { doAndWaitForImageDiff } from '../test-utils'
|
import {
|
||||||
|
checkIfPaneIsOpen,
|
||||||
|
closePane,
|
||||||
|
doAndWaitForImageDiff,
|
||||||
|
openPane,
|
||||||
|
} from '../test-utils'
|
||||||
|
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
|
||||||
|
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
|
||||||
|
|
||||||
export class ToolbarFixture {
|
export class ToolbarFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
|
|
||||||
extrudeButton!: Locator
|
extrudeButton!: Locator
|
||||||
loftButton!: Locator
|
loftButton!: Locator
|
||||||
|
sweepButton!: Locator
|
||||||
|
filletButton!: Locator
|
||||||
|
chamferButton!: Locator
|
||||||
|
shellButton!: Locator
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
lineBtn!: Locator
|
lineBtn!: Locator
|
||||||
@ -19,6 +30,10 @@ export class ToolbarFixture {
|
|||||||
filePane!: Locator
|
filePane!: Locator
|
||||||
exeIndicator!: Locator
|
exeIndicator!: Locator
|
||||||
treeInputField!: 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) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
@ -28,6 +43,10 @@ export class ToolbarFixture {
|
|||||||
this.page = page
|
this.page = page
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
this.loftButton = page.getByTestId('loft')
|
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.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
this.lineBtn = page.getByTestId('line')
|
this.lineBtn = page.getByTestId('line')
|
||||||
@ -39,10 +58,15 @@ export class ToolbarFixture {
|
|||||||
this.treeInputField = page.getByTestId('tree-input-field')
|
this.treeInputField = page.getByTestId('tree-input-field')
|
||||||
|
|
||||||
this.filePane = page.locator('#files-pane')
|
this.filePane = page.locator('#files-pane')
|
||||||
|
this.featureTreePane = page.locator('#feature-tree-pane')
|
||||||
this.fileCreateToast = page.getByText('Successfully created')
|
this.fileCreateToast = page.getByText('Successfully created')
|
||||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get logoLink() {
|
||||||
|
return this.page.getByTestId('app-logo')
|
||||||
|
}
|
||||||
|
|
||||||
startSketchPlaneSelection = async () =>
|
startSketchPlaneSelection = async () =>
|
||||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||||
|
|
||||||
@ -89,4 +113,76 @@ export class ToolbarFixture {
|
|||||||
await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 })
|
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 { test, expect } from './zoo-test'
|
||||||
import { setupElectron, tearDown, executorInputPath } from './test-utils'
|
import { executorInputPath } from './test-utils'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'When machine-api server not found butt is disabled and shows the reason',
|
'When machine-api server not found butt is disabled and shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await context.folderSetupFn(async (dir) => {
|
||||||
testInfo,
|
const bracketDir = join(dir, 'bracket')
|
||||||
folderSetupFn: async (dir) => {
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
const bracketDir = join(dir, 'bracket')
|
await fsp.copyFile(
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
await fsp.copyFile(
|
join(bracketDir, 'main.kcl')
|
||||||
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()
|
await expect(page.getByText('bracket')).toBeVisible()
|
||||||
|
|
||||||
@ -47,28 +40,23 @@ test(
|
|||||||
// that the machine-api server is not found
|
// that the machine-api server is not found
|
||||||
await makeButton.hover()
|
await makeButton.hover()
|
||||||
await expect(page.getByText(notFoundText).first()).toBeVisible()
|
await expect(page.getByText(notFoundText).first()).toBeVisible()
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'When machine-api server not found home screen & project status shows the reason',
|
'When machine-api server not found home screen & project status shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await context.folderSetupFn(async (dir) => {
|
||||||
testInfo,
|
const bracketDir = join(dir, 'bracket')
|
||||||
folderSetupFn: async (dir) => {
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
const bracketDir = join(dir, 'bracket')
|
await fsp.copyFile(
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
await fsp.copyFile(
|
join(bracketDir, 'main.kcl')
|
||||||
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'
|
const notFoundText = 'Machine API server was not discovered'
|
||||||
|
|
||||||
@ -91,7 +79,5 @@ test(
|
|||||||
|
|
||||||
await networkMachineToggle.hover()
|
await networkMachineToggle.hover()
|
||||||
await expect(page.getByText(notFoundText).nth(1)).toBeVisible()
|
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 { join } from 'path'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import {
|
import { getUtils, executorInputPath, createProject } from './test-utils'
|
||||||
getUtils,
|
|
||||||
setup,
|
|
||||||
setupElectron,
|
|
||||||
tearDown,
|
|
||||||
executorInputPath,
|
|
||||||
createProject,
|
|
||||||
} from './test-utils'
|
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import {
|
import {
|
||||||
TEST_SETTINGS_KEY,
|
TEST_SETTINGS_KEY,
|
||||||
TEST_SETTINGS_ONBOARDING_START,
|
TEST_SETTINGS_ONBOARDING_START,
|
||||||
TEST_SETTINGS_ONBOARDING_EXPORT,
|
TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||||
TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING,
|
|
||||||
TEST_SETTINGS_ONBOARDING_USER_MENU,
|
TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||||
} from './storageStates'
|
} from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
|
import { expectPixelColor } from './fixtures/sceneFixture'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
// Because onboarding relies on an app setting we need to set it as incompletel
|
||||||
if (testInfo.tags.includes('@electron')) {
|
// for all these tests.
|
||||||
return
|
|
||||||
}
|
|
||||||
await setup(context, page)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Onboarding tests', () => {
|
test.describe('Onboarding tests', () => {
|
||||||
test('Onboarding code is shown in the editor', async ({ page }) => {
|
test(
|
||||||
const u = await getUtils(page)
|
'Onboarding code is shown in the editor',
|
||||||
|
{
|
||||||
// Override beforeEach test setup
|
appSettings: {
|
||||||
await page.addInitScript(
|
app: {
|
||||||
async ({ settingsKey }) => {
|
onboardingStatus: 'incomplete',
|
||||||
// Give no initial code, so that the onboarding start is shown immediately
|
},
|
||||||
localStorage.removeItem('persistCode')
|
|
||||||
localStorage.removeItem(settingsKey)
|
|
||||||
},
|
},
|
||||||
{ settingsKey: TEST_SETTINGS_KEY }
|
cleanProjectDir: true,
|
||||||
)
|
},
|
||||||
|
async ({ context, page, homePage }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
// Test that the onboarding pane loaded
|
||||||
|
await expect(
|
||||||
|
page.getByText('Welcome to Modeling App! This')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
// Test that the onboarding pane loaded
|
||||||
|
await expect(
|
||||||
|
page.getByText('Welcome to Modeling App! This')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// *and* that the code is shown in the editor
|
||||||
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
'// Shelf Bracket'
|
||||||
|
)
|
||||||
|
|
||||||
// *and* that the code is shown in the editor
|
// Make sure the model loaded
|
||||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
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(
|
test(
|
||||||
'Desktop: fresh onboarding executes and loads',
|
'Desktop: fresh onboarding executes and loads',
|
||||||
{ tag: '@electron' },
|
{
|
||||||
async ({ browserName: _ }, testInfo) => {
|
tag: '@electron',
|
||||||
const { electronApp, page } = await setupElectron({
|
appSettings: {
|
||||||
testInfo,
|
app: {
|
||||||
appSettings: {
|
onboardingStatus: 'incomplete',
|
||||||
app: {
|
|
||||||
onboardingStatus: 'incomplete',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
cleanProjectDir: true,
|
},
|
||||||
})
|
cleanProjectDir: true,
|
||||||
|
},
|
||||||
|
async ({ page, homePage }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
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 test.step(`Create a project and open to the onboarding`, async () => {
|
||||||
await createProject({ name: 'project-link', page })
|
await createProject({ name: 'project-link', page })
|
||||||
@ -92,321 +91,370 @@ test.describe('Onboarding tests', () => {
|
|||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
'// Shelf Bracket'
|
'// 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(
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')`
|
'Code resets after confirmation',
|
||||||
|
{
|
||||||
// Load the page up with some code so we see the confirmation warning
|
appSettings: {
|
||||||
// when we go to replay onboarding
|
app: {
|
||||||
await page.addInitScript((code) => {
|
onboardingStatus: 'incomplete',
|
||||||
localStorage.setItem('persistCode', code)
|
},
|
||||||
}, initialCode)
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
// Replay the onboarding
|
|
||||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
|
||||||
const replayButton = page.getByRole('button', { name: 'Replay onboarding' })
|
|
||||||
await expect(replayButton).toBeVisible()
|
|
||||||
await replayButton.click()
|
|
||||||
|
|
||||||
// Ensure we see the warning, and that the code has not yet updated
|
|
||||||
await expect(
|
|
||||||
page.getByText('Replaying onboarding resets your code')
|
|
||||||
).toBeVisible()
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(initialCode)
|
|
||||||
|
|
||||||
const nextButton = page.getByTestId('onboarding-next')
|
|
||||||
await expect(nextButton).toBeVisible()
|
|
||||||
await nextButton.click()
|
|
||||||
|
|
||||||
// Ensure we see the introduction and that the code has been reset
|
|
||||||
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
|
||||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
|
||||||
|
|
||||||
// Ensure we persisted the code to local storage.
|
|
||||||
// Playwright's addInitScript method unfortunately will reset
|
|
||||||
// this code if we try reloading the page as a test,
|
|
||||||
// so this is our best way to test persistence afaik.
|
|
||||||
expect(
|
|
||||||
await page.evaluate(() => {
|
|
||||||
return localStorage.getItem('persistCode')
|
|
||||||
})
|
|
||||||
).toContain('// Shelf Bracket')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Click through each onboarding step', async ({ page }) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
// Override beforeEach test setup
|
|
||||||
await page.addInitScript(
|
|
||||||
async ({ settingsKey, settings }) => {
|
|
||||||
// Give no initial code, so that the onboarding start is shown immediately
|
|
||||||
localStorage.setItem('persistCode', '')
|
|
||||||
localStorage.setItem(settingsKey, settings)
|
|
||||||
},
|
},
|
||||||
{
|
cleanProjectDir: true,
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
},
|
||||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }),
|
async ({ context, page, homePage }) => {
|
||||||
}
|
const initialCode = `sketch001 = startSketchOn('XZ')`
|
||||||
)
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
// Load the page up with some code so we see the confirmation warning
|
||||||
|
// when we go to replay onboarding
|
||||||
|
await context.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, initialCode)
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// Replay the onboarding
|
||||||
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||||
|
const replayButton = page.getByRole('button', {
|
||||||
|
name: 'Replay onboarding',
|
||||||
|
})
|
||||||
|
await expect(replayButton).toBeVisible()
|
||||||
|
await replayButton.click()
|
||||||
|
|
||||||
const nextButton = page.getByTestId('onboarding-next')
|
// Ensure we see the warning, and that the code has not yet updated
|
||||||
|
await expect(page.getByText('Would you like to create')).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(initialCode)
|
||||||
|
|
||||||
while ((await nextButton.innerText()) !== 'Finish') {
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
await expect(nextButton).toBeVisible()
|
await nextButton.hover()
|
||||||
await nextButton.click()
|
await nextButton.click()
|
||||||
|
|
||||||
|
// Ensure we see the introduction and that the code has been reset
|
||||||
|
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
'// Shelf Bracket'
|
||||||
|
)
|
||||||
|
|
||||||
|
// There used to be old code here that checked if we stored the reset
|
||||||
|
// code into localStorage but that isn't the case on desktop. It gets
|
||||||
|
// saved to the file system, which we have other tests for.
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Finish the onboarding
|
test(
|
||||||
await expect(nextButton).toBeVisible()
|
'Click through each onboarding step',
|
||||||
await nextButton.click()
|
{
|
||||||
|
appSettings: {
|
||||||
// Test that the onboarding pane is gone
|
app: {
|
||||||
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
onboardingStatus: 'incomplete',
|
||||||
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,
|
async ({ context, page, homePage }) => {
|
||||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }),
|
// Override beforeEach test setup
|
||||||
|
await context.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
// Give no initial code, so that the onboarding start is shown immediately
|
||||||
|
localStorage.setItem('persistCode', '')
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({
|
||||||
|
settings: TEST_SETTINGS_ONBOARDING_START,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 1080 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// Test that the onboarding pane loaded
|
||||||
|
await expect(
|
||||||
|
page.getByText('Welcome to Modeling App! This')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
|
|
||||||
|
while ((await nextButton.innerText()) !== 'Finish') {
|
||||||
|
await nextButton.hover()
|
||||||
|
await nextButton.click()
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
// Finish the onboarding
|
||||||
|
await nextButton.hover()
|
||||||
|
await nextButton.click()
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
// Test that the onboarding pane is gone
|
||||||
|
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||||
// Test that the redirect happened
|
await expect.poll(() => page.url()).not.toContain('/onboarding')
|
||||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
|
||||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test that you come back to this page when you refresh
|
|
||||||
await page.reload()
|
|
||||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
|
||||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
|
||||||
const title = page.locator('[data-testid="onboarding-content"]')
|
|
||||||
await expect(title).toBeAttached()
|
|
||||||
|
|
||||||
// Test that the code changes when you advance to the next step
|
|
||||||
await page.locator('[data-testid="onboarding-next"]').click()
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText('')
|
|
||||||
|
|
||||||
// Test that the code is not empty when you click on the next step
|
|
||||||
await page.locator('[data-testid="onboarding-next"]').click()
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Onboarding code gets reset to demo on Interactive Numbers step', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
test.skip(
|
|
||||||
process.platform === 'darwin',
|
|
||||||
"Skip on macOS, because Playwright isn't behaving the same as the actual browser"
|
|
||||||
)
|
|
||||||
const u = await getUtils(page)
|
|
||||||
const badCode = `// This is bad code we shouldn't see`
|
|
||||||
// Override beforeEach test setup
|
|
||||||
await page.addInitScript(
|
|
||||||
async ({ settingsKey, settings, badCode }) => {
|
|
||||||
localStorage.setItem('persistCode', badCode)
|
|
||||||
localStorage.setItem(settingsKey, settings)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
|
||||||
settings: TOML.stringify({
|
|
||||||
settings: TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING,
|
|
||||||
}),
|
|
||||||
badCode,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await page.waitForURL('**' + onboardingPaths.PARAMETRIC_MODELING, {
|
|
||||||
waitUntil: 'domcontentloaded',
|
|
||||||
})
|
|
||||||
|
|
||||||
const bracketNoNewLines = bracket.replace(/\n/g, '')
|
|
||||||
|
|
||||||
// Check the code got reset on load
|
|
||||||
await expect(page.locator('#code-pane')).toBeVisible()
|
|
||||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Mess with the code again
|
|
||||||
await u.codeLocator.selectText()
|
|
||||||
await u.codeLocator.fill(badCode)
|
|
||||||
await expect(u.codeLocator).toHaveText(badCode)
|
|
||||||
|
|
||||||
// Click to the next step
|
|
||||||
await page.locator('[data-testid="onboarding-next"]').click()
|
|
||||||
await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, {
|
|
||||||
waitUntil: 'domcontentloaded',
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check that the code has been reset
|
|
||||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Avatar text updates depending on image load success', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
// Override beforeEach test setup
|
|
||||||
await page.addInitScript(
|
|
||||||
async ({ settingsKey, settings }) => {
|
|
||||||
localStorage.setItem(settingsKey, settings)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
|
||||||
settings: TOML.stringify({
|
|
||||||
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
|
||||||
const avatarLocator = await page
|
|
||||||
.getByTestId('user-sidebar-toggle')
|
|
||||||
.locator('img')
|
|
||||||
const onboardingOverlayLocator = await page
|
|
||||||
.getByTestId('onboarding-content')
|
|
||||||
.locator('div')
|
|
||||||
.nth(1)
|
|
||||||
|
|
||||||
// Expect the avatar to be visible and for the text to reference it
|
|
||||||
await expect(avatarLocator).toBeVisible()
|
|
||||||
await expect(onboardingOverlayLocator).toBeVisible()
|
|
||||||
await expect(onboardingOverlayLocator).toContainText('your avatar')
|
|
||||||
|
|
||||||
// This is to force the avatar to 404.
|
|
||||||
// For our test image (only triggers locally. on CI, it's Kurt's /
|
|
||||||
// gravatar image )
|
|
||||||
await page.route('/cat.jpg', async (route) => {
|
|
||||||
await route.fulfill({
|
|
||||||
status: 404,
|
|
||||||
contentType: 'text/plain',
|
|
||||||
body: 'Not Found!',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 404 the CI avatar image
|
|
||||||
await page.route('https://lh3.googleusercontent.com/**', async (route) => {
|
|
||||||
await route.fulfill({
|
|
||||||
status: 404,
|
|
||||||
contentType: 'text/plain',
|
|
||||||
body: 'Not Found!',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.reload({ waitUntil: 'domcontentloaded' })
|
|
||||||
|
|
||||||
// Now expect the text to be different
|
|
||||||
await expect(avatarLocator).not.toBeVisible()
|
|
||||||
await expect(onboardingOverlayLocator).toBeVisible()
|
|
||||||
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Avatar text doesn't mention avatar when no avatar", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
// Override beforeEach test setup
|
|
||||||
await page.addInitScript(
|
|
||||||
async ({ settingsKey, settings }) => {
|
|
||||||
localStorage.setItem(settingsKey, settings)
|
|
||||||
localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
|
||||||
settings: TOML.stringify({
|
|
||||||
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
|
||||||
const sidebar = page.getByTestId('user-sidebar-toggle')
|
|
||||||
const avatar = sidebar.locator('img')
|
|
||||||
const onboardingOverlayLocator = page
|
|
||||||
.getByTestId('onboarding-content')
|
|
||||||
.locator('div')
|
|
||||||
.nth(1)
|
|
||||||
|
|
||||||
// Expect the avatar to be visible and for the text to reference it
|
|
||||||
await expect(avatar).not.toBeVisible()
|
|
||||||
await expect(onboardingOverlayLocator).toBeVisible()
|
|
||||||
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
|
||||||
|
|
||||||
// Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939
|
|
||||||
// which doesn't deserver its own full test spun up
|
|
||||||
const userMenuFeatures = [
|
|
||||||
'manage your account',
|
|
||||||
'report a bug',
|
|
||||||
'request a feature',
|
|
||||||
'sign out',
|
|
||||||
]
|
|
||||||
for (const feature of userMenuFeatures) {
|
|
||||||
await expect(onboardingOverlayLocator).toContainText(feature)
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Onboarding redirects and code updating',
|
||||||
|
{
|
||||||
|
appSettings: {
|
||||||
|
app: {
|
||||||
|
onboardingStatus: '/export',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cleanProjectDir: true,
|
||||||
|
},
|
||||||
|
async ({ context, page, homePage }) => {
|
||||||
|
const originalCode = 'sigmaAllow = 15000'
|
||||||
|
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await context.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
// Give some initial code, so we can test that it's cleared
|
||||||
|
localStorage.setItem('persistCode', originalCode)
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({
|
||||||
|
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// Test that the redirect happened
|
||||||
|
await expect.poll(() => page.url()).toContain('/onboarding/export')
|
||||||
|
|
||||||
|
// Test that you come back to this page when you refresh
|
||||||
|
await page.reload()
|
||||||
|
await expect.poll(() => page.url()).toContain('/onboarding/export')
|
||||||
|
|
||||||
|
// Test that the code changes when you advance to the next step
|
||||||
|
await page.getByTestId('onboarding-next').hover()
|
||||||
|
await page.getByTestId('onboarding-next').click()
|
||||||
|
|
||||||
|
// Test that the onboarding pane loaded
|
||||||
|
const title = page.locator('[data-testid="onboarding-content"]')
|
||||||
|
await expect(title).toBeAttached()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(originalCode)
|
||||||
|
|
||||||
|
// Test that the code is not empty when you click on the next step
|
||||||
|
await page.locator('[data-testid="onboarding-next"]').hover()
|
||||||
|
await page.locator('[data-testid="onboarding-next"]').click()
|
||||||
|
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Onboarding code gets reset to demo on Interactive Numbers step',
|
||||||
|
{
|
||||||
|
appSettings: {
|
||||||
|
app: {
|
||||||
|
onboardingStatus: '/parametric-modeling',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cleanProjectDir: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
async ({ context, page, homePage }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const badCode = `// This is bad code we shouldn't see`
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 1080 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(() => page.url())
|
||||||
|
.toContain(onboardingPaths.PARAMETRIC_MODELING)
|
||||||
|
|
||||||
|
const bracketNoNewLines = bracket.replace(/\n/g, '')
|
||||||
|
|
||||||
|
// Check the code got reset on load
|
||||||
|
await expect(page.locator('#code-pane')).toBeVisible()
|
||||||
|
await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mess with the code again
|
||||||
|
await u.codeLocator.selectText()
|
||||||
|
await u.codeLocator.fill(badCode)
|
||||||
|
await expect(u.codeLocator).toHaveText(badCode)
|
||||||
|
|
||||||
|
// Click to the next step
|
||||||
|
await page.locator('[data-testid="onboarding-next"]').hover()
|
||||||
|
await page.locator('[data-testid="onboarding-next"]').click()
|
||||||
|
await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, {
|
||||||
|
waitUntil: 'domcontentloaded',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check that the code has been reset
|
||||||
|
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// (lee) The two avatar tests are weird because even on main, we don't have
|
||||||
|
// anything to do with the avatar inside the onboarding test. Due to the
|
||||||
|
// low impact of an avatar not showing I'm changing this to fixme.
|
||||||
|
test.fixme(
|
||||||
|
'Avatar text updates depending on image load success',
|
||||||
|
{
|
||||||
|
appSettings: {
|
||||||
|
app: {
|
||||||
|
onboardingStatus: 'incomplete',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cleanProjectDir: true,
|
||||||
|
},
|
||||||
|
async ({ context, page, homePage }) => {
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await context.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({
|
||||||
|
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// Test that the text in this step is correct
|
||||||
|
const avatarLocator = await page
|
||||||
|
.getByTestId('user-sidebar-toggle')
|
||||||
|
.locator('img')
|
||||||
|
const onboardingOverlayLocator = await page
|
||||||
|
.getByTestId('onboarding-content')
|
||||||
|
.locator('div')
|
||||||
|
.nth(1)
|
||||||
|
|
||||||
|
// Expect the avatar to be visible and for the text to reference it
|
||||||
|
await expect(avatarLocator).toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toContainText('your avatar')
|
||||||
|
|
||||||
|
// This is to force the avatar to 404.
|
||||||
|
// For our test image (only triggers locally. on CI, it's Kurt's /
|
||||||
|
// gravatar image )
|
||||||
|
await page.route('/cat.jpg', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 404,
|
||||||
|
contentType: 'text/plain',
|
||||||
|
body: 'Not Found!',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 404 the CI avatar image
|
||||||
|
await page.route(
|
||||||
|
'https://lh3.googleusercontent.com/**',
|
||||||
|
async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 404,
|
||||||
|
contentType: 'text/plain',
|
||||||
|
body: 'Not Found!',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
|
// Now expect the text to be different
|
||||||
|
await expect(avatarLocator).not.toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test.fixme(
|
||||||
|
"Avatar text doesn't mention avatar when no avatar",
|
||||||
|
{
|
||||||
|
appSettings: {
|
||||||
|
app: {
|
||||||
|
onboardingStatus: 'incomplete',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cleanProjectDir: true,
|
||||||
|
},
|
||||||
|
async ({ context, page, homePage }) => {
|
||||||
|
// Override beforeEach test setup
|
||||||
|
await context.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({
|
||||||
|
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// Test that the text in this step is correct
|
||||||
|
const sidebar = page.getByTestId('user-sidebar-toggle')
|
||||||
|
const avatar = sidebar.locator('img')
|
||||||
|
const onboardingOverlayLocator = page
|
||||||
|
.getByTestId('onboarding-content')
|
||||||
|
.locator('div')
|
||||||
|
.nth(1)
|
||||||
|
|
||||||
|
// Expect the avatar to be visible and for the text to reference it
|
||||||
|
await expect(avatar).not.toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toBeVisible()
|
||||||
|
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
||||||
|
|
||||||
|
// Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939
|
||||||
|
// which doesn't deserver its own full test spun up
|
||||||
|
const userMenuFeatures = [
|
||||||
|
'manage your account',
|
||||||
|
'report a bug',
|
||||||
|
'request a feature',
|
||||||
|
'sign out',
|
||||||
|
]
|
||||||
|
for (const feature of userMenuFeatures) {
|
||||||
|
await expect(onboardingOverlayLocator).toContainText(feature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test.fixme(
|
||||||
'Restarting onboarding on desktop takes one attempt',
|
'Restarting onboarding on desktop takes one attempt',
|
||||||
{ tag: '@electron' },
|
{
|
||||||
async ({ browser: _ }, testInfo) => {
|
appSettings: {
|
||||||
const { electronApp, page } = await setupElectron({
|
app: {
|
||||||
testInfo,
|
onboardingStatus: 'dismissed',
|
||||||
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')
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
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
|
// Our constants
|
||||||
@ -418,9 +466,8 @@ test(
|
|||||||
const restartOnboardingButton = page.getByRole('button', {
|
const restartOnboardingButton = page.getByRole('button', {
|
||||||
name: 'Reset onboarding',
|
name: 'Reset onboarding',
|
||||||
})
|
})
|
||||||
const restartConfirmationButton = page.getByRole('button', {
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
name: 'Make a new project',
|
|
||||||
})
|
|
||||||
const tutorialProjectIndicator = page
|
const tutorialProjectIndicator = page
|
||||||
.getByTestId('project-sidebar-toggle')
|
.getByTestId('project-sidebar-toggle')
|
||||||
.filter({ hasText: 'Tutorial Project 00' })
|
.filter({ hasText: 'Tutorial Project 00' })
|
||||||
@ -439,7 +486,7 @@ test(
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Navigate into project', async () => {
|
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)
|
page.on('console', console.log)
|
||||||
|
|
||||||
@ -455,14 +502,22 @@ test(
|
|||||||
await helpMenuButton.click()
|
await helpMenuButton.click()
|
||||||
await restartOnboardingButton.click()
|
await restartOnboardingButton.click()
|
||||||
|
|
||||||
await expect(restartConfirmationButton).toBeVisible()
|
await nextButton.hover()
|
||||||
await restartConfirmationButton.click()
|
await nextButton.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Confirm that the onboarding has restarted', async () => {
|
await test.step('Confirm that the onboarding has restarted', async () => {
|
||||||
await expect(tutorialProjectIndicator).toBeVisible()
|
await expect(tutorialProjectIndicator).toBeVisible()
|
||||||
await expect(tutorialModalText).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()
|
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 () => {
|
await test.step('Clear code and restart onboarding from settings', async () => {
|
||||||
@ -480,11 +535,9 @@ test(
|
|||||||
|
|
||||||
await restartOnboardingSettingsButton.click()
|
await restartOnboardingSettingsButton.click()
|
||||||
// Since the code is empty, we should not see the confirmation dialog
|
// 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(tutorialProjectIndicator).toBeVisible()
|
||||||
await expect(tutorialModalText).toBeVisible()
|
await expect(tutorialModalText).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
190
e2e/playwright/prompt-to-edit.spec.ts
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
|
/* eslint-disable jest/no-conditional-expect */
|
||||||
|
|
||||||
|
const file = `sketch001 = startSketchOn('XZ')
|
||||||
|
profile001 = startProfileAt([57.81, 250.51], sketch001)
|
||||||
|
|> line([121.13, 56.63], %, $seg02)
|
||||||
|
|> line([83.37, -34.61], %, $seg01)
|
||||||
|
|> line([19.66, -116.4], %)
|
||||||
|
|> line([-221.8, -41.69], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(200, profile001)
|
||||||
|
sketch002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-73.64, -42.89], %)
|
||||||
|
|> xLine(173.71, %)
|
||||||
|
|> line([-22.12, -94.4], %)
|
||||||
|
|> xLine(-156.98, %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude002 = extrude(50, sketch002)
|
||||||
|
sketch003 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([52.92, 157.81], %)
|
||||||
|
|> angledLine([0, 176.4], %, $rectangleSegmentA001)
|
||||||
|
|> angledLine([
|
||||||
|
segAng(rectangleSegmentA001) - 90,
|
||||||
|
53.4
|
||||||
|
], %, $rectangleSegmentB001)
|
||||||
|
|> angledLine([
|
||||||
|
segAng(rectangleSegmentA001),
|
||||||
|
-segLen(rectangleSegmentA001)
|
||||||
|
], %, $rectangleSegmentC001)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude003 = extrude(20, sketch003)
|
||||||
|
`
|
||||||
|
|
||||||
|
test.describe('Check the happy path, for basic changing color', () => {
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
desc: 'User accepts change',
|
||||||
|
shouldReject: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: 'User rejects change',
|
||||||
|
shouldReject: true,
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
for (const { desc, shouldReject } of cases) {
|
||||||
|
test(`${desc}`, async ({
|
||||||
|
context,
|
||||||
|
homePage,
|
||||||
|
cmdBar,
|
||||||
|
editor,
|
||||||
|
page,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
|
await context.addInitScript((file) => {
|
||||||
|
localStorage.setItem('persistCode', file)
|
||||||
|
}, file)
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
const body1CapCoords = { x: 571, y: 351 }
|
||||||
|
const greenCheckCoords = { x: 565, y: 345 }
|
||||||
|
const body2WallCoords = { x: 609, y: 153 }
|
||||||
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
|
body1CapCoords.x,
|
||||||
|
body1CapCoords.y
|
||||||
|
)
|
||||||
|
const yellow: [number, number, number] = [179, 179, 131]
|
||||||
|
const green: [number, number, number] = [108, 152, 75]
|
||||||
|
const notGreen: [number, number, number] = [132, 132, 132]
|
||||||
|
const body2NotGreen: [number, number, number] = [88, 88, 88]
|
||||||
|
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
||||||
|
const successToast = page.getByText('Prompt to edit successful')
|
||||||
|
const acceptBtn = page.getByRole('button', { name: 'checkmark Accept' })
|
||||||
|
const rejectBtn = page.getByRole('button', { name: 'close Reject' })
|
||||||
|
|
||||||
|
await test.step('wait for scene to load select body and check selection came through', async () => {
|
||||||
|
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
||||||
|
await clickBody1Cap()
|
||||||
|
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||||
|
await editor.expectState({
|
||||||
|
highlightedCode: '',
|
||||||
|
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
||||||
|
diagnostics: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('fire off edit prompt', async () => {
|
||||||
|
await cmdBar.openCmdBar('promptToEdit')
|
||||||
|
// being specific about the color with a hex means asserting pixel color is more stable
|
||||||
|
await page
|
||||||
|
.getByTestId('cmd-bar-arg-value')
|
||||||
|
.fill('make this neon green please, use #39FF14')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await expect(submittingToast).toBeVisible()
|
||||||
|
await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while
|
||||||
|
await expect(successToast).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('verify initial change', async () => {
|
||||||
|
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||||
|
await scene.expectPixelColor(body2NotGreen, body2WallCoords, 15)
|
||||||
|
await editor.expectEditor.toContain('appearance({')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!shouldReject) {
|
||||||
|
await test.step('check accept works and can be "undo"ed', async () => {
|
||||||
|
await acceptBtn.click()
|
||||||
|
await expect(successToast).not.toBeVisible()
|
||||||
|
|
||||||
|
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||||
|
await editor.expectEditor.toContain('appearance({')
|
||||||
|
|
||||||
|
// ctrl-z works after accepting
|
||||||
|
await page.keyboard.down('ControlOrMeta')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
await editor.expectEditor.not.toContain('appearance({')
|
||||||
|
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await test.step('check reject works', async () => {
|
||||||
|
await rejectBtn.click()
|
||||||
|
await expect(successToast).not.toBeVisible()
|
||||||
|
|
||||||
|
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||||
|
await editor.expectEditor.not.toContain('appearance({')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('bad path', () => {
|
||||||
|
test(`bad edit prompt`, async ({
|
||||||
|
context,
|
||||||
|
homePage,
|
||||||
|
cmdBar,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
page,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
|
await context.addInitScript((file) => {
|
||||||
|
localStorage.setItem('persistCode', file)
|
||||||
|
}, file)
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
const body1CapCoords = { x: 571, y: 351 }
|
||||||
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
|
body1CapCoords.x,
|
||||||
|
body1CapCoords.y
|
||||||
|
)
|
||||||
|
const yellow: [number, number, number] = [179, 179, 131]
|
||||||
|
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
||||||
|
const failToast = page.getByText(
|
||||||
|
'Failed to edit your KCL code, please try again with a different prompt or selection'
|
||||||
|
)
|
||||||
|
|
||||||
|
await test.step('wait for scene to load and select body', async () => {
|
||||||
|
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
||||||
|
|
||||||
|
await clickBody1Cap()
|
||||||
|
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||||
|
|
||||||
|
await editor.expectState({
|
||||||
|
highlightedCode: '',
|
||||||
|
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
||||||
|
diagnostics: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('fire of bad prompt', async () => {
|
||||||
|
await cmdBar.openCmdBar('promptToEdit')
|
||||||
|
await page
|
||||||
|
.getByTestId('cmd-bar-arg-value')
|
||||||
|
.fill('ansheusha asnthuatshoeuhtaoetuhthaeu laughs in dvorak')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await expect(submittingToast).toBeVisible()
|
||||||
|
})
|
||||||
|
await test.step('check fail toast appeared', async () => {
|
||||||
|
await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while
|
||||||
|
await expect(failToast).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,46 +1,37 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page } from './zoo-test'
|
||||||
import { join } from 'path'
|
import path from 'path'
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
import {
|
import { getUtils, executorInputPath } from './test-utils'
|
||||||
getUtils,
|
|
||||||
setup,
|
|
||||||
setupElectron,
|
|
||||||
tearDown,
|
|
||||||
executorInputPath,
|
|
||||||
} from './test-utils'
|
|
||||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
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', () => {
|
test.describe('Regression tests', () => {
|
||||||
// bugs we found that don't fit neatly into other categories
|
// 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
|
// 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
|
// 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
|
// Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch2 = startSketchOn("XY")
|
`sketch2 = startSketchOn("XY")
|
||||||
sketch001 = startSketchAt([-0, -0])
|
sketch001 = startSketchAt([-0, -0])
|
||||||
|> line([0, 0], %)
|
|> line([0, 0], %)
|
||||||
|> line([-4.84, -5.29], %)
|
|> line([-4.84, -5.29], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`
|
|> close(%)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// error in guter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
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 ({
|
test('user should not have to press down twice in cmdbar', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
// 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
|
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
||||||
@ -64,26 +56,38 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch2 = startSketchOn("XY")
|
`sketch001 = startSketchOn('XY')
|
||||||
sketch001 = startSketchAt([-0, -0])
|
|> startProfileAt([82.33, 238.21], %)
|
||||||
|> line([0, 0], %)
|
|> angledLine([0, 288.63], %, $rectangleSegmentA001)
|
||||||
|> line([-4.84, -5.29], %)
|
|> angledLine([
|
||||||
|
segAng(rectangleSegmentA001) - 90,
|
||||||
|
197.97
|
||||||
|
], %, $rectangleSegmentB001)
|
||||||
|
|> angledLine([
|
||||||
|
segAng(rectangleSegmentA001),
|
||||||
|
-segLen(rectangleSegmentA001)
|
||||||
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> 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 u.waitForPageLoad()
|
||||||
|
|
||||||
await test.step('Check arrow down works', async () => {
|
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.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
await page
|
const floppy = page.getByRole('option', {
|
||||||
.getByRole('option', { name: 'floppy disk arrow Export' })
|
name: 'floppy disk arrow Export',
|
||||||
.click()
|
})
|
||||||
|
|
||||||
|
await floppy.click()
|
||||||
|
|
||||||
// press arrow down key twice
|
// press arrow down key twice
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
@ -115,21 +119,22 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
test('executes on load', async ({ page }) => {
|
test('executes on load', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('-XZ')
|
`sketch001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([-6.95, 4.98], %)
|
|> startProfileAt([-6.95, 4.98], %)
|
||||||
|> line([25.1, 0.41], %)
|
|> line([25.1, 0.41], %)
|
||||||
|> line([0.73, -14.93], %)
|
|> line([0.73, -14.93], %)
|
||||||
|> line([-23.44, 0.52], %)`
|
|> 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
|
// expand variables section
|
||||||
const variablesTabButton = page.getByTestId('variables-pane-button')
|
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||||
@ -148,14 +153,15 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('re-executes', async ({ page }) => {
|
test('re-executes', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem('persistCode', `myVar = 5`)
|
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')
|
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||||
await variablesTabButton.click()
|
await variablesTabButton.click()
|
||||||
@ -174,32 +180,33 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
page.locator('.pretty-json-container >> text=myVar:67')
|
page.locator('.pretty-json-container >> text=myVar:67')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
test('ProgramMemory can be serialised', async ({ page }) => {
|
test('ProgramMemory can be serialised', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`part = startSketchOn('XY')
|
`part = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([0, 1], %)
|
|> line([0, 1], %)
|
||||||
|> line([1, 0], %)
|
|> line([1, 0], %)
|
||||||
|> line([0, -1], %)
|
|> line([0, -1], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(1, %)
|
|> extrude(1, %)
|
||||||
|> patternLinear3d({
|
|> patternLinear3d({
|
||||||
axis: [1, 0, 1],
|
axis: [1, 0, 1],
|
||||||
repetitions: 3,
|
repetitions: 3,
|
||||||
distance: 6
|
distance: 6
|
||||||
}, %)`
|
}, %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
const messages: string[] = []
|
const messages: string[] = []
|
||||||
|
|
||||||
// Listen for all console events and push the message text to an array
|
// Listen for all console events and push the message text to an array
|
||||||
page.on('console', (message) => messages.push(message.text()))
|
page.on('console', (message) => messages.push(message.text()))
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
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)
|
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()
|
||||||
|
|
||||||
const zooLogo = page.locator('[data-testid="app-logo"]')
|
const zooLogo = page.locator('[data-testid="app-logo"]')
|
||||||
// Make sure it's not a link
|
// Make sure it's not a link
|
||||||
await expect(zooLogo).not.toHaveAttribute('href')
|
await expect(zooLogo).not.toHaveAttribute('href')
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Position _ Is Out Of Range... regression test',
|
'Position _ Is Out Of Range... regression test',
|
||||||
{ tag: ['@skipWin'] },
|
{ tag: ['@skipWin'] },
|
||||||
async ({ page }) => {
|
async ({ context, page, homePage }) => {
|
||||||
// SKip on windows, its being weird.
|
// SKip on windows, its being weird.
|
||||||
test.skip(
|
test.skip(
|
||||||
process.platform === 'win32',
|
process.platform === 'win32',
|
||||||
@ -233,25 +247,26 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await page.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`exampleSketch = startSketchOn("XZ")
|
`exampleSketch = startSketchOn("XZ")
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine({ angle: 50, length: 45 }, %)
|
|> angledLine({ angle: 50, length: 45 }, %)
|
||||||
|> yLineTo(0, %)
|
|> yLineTo(0, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|>
|
|>
|
||||||
|
|
||||||
example = extrude(5, exampleSketch)
|
example = extrude(5, exampleSketch)
|
||||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await page.goto('/')
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// error in guter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||||
timeout: 1_000,
|
timeout: 1_000,
|
||||||
@ -293,12 +308,12 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toContainText(`exampleSketch = startSketchOn("XZ")
|
.toContainText(`exampleSketch = startSketchOn("XZ")
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine({ angle: 50, length: 45 }, %)
|
|> angledLine({ angle: 50, length: 45 }, %)
|
||||||
|> yLineTo(0, %)
|
|> yLineTo(0, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|
|
||||||
thing: "blah"`)
|
thing: "blah"`)
|
||||||
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
}
|
}
|
||||||
@ -306,6 +321,7 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
|
|
||||||
test('when engine fails export we handle the failure and alert the user', async ({
|
test('when engine fails export we handle the failure and alert the user', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
@ -316,9 +332,10 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
|
{ 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
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -374,7 +391,6 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.clearCommandLogs()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
@ -408,7 +424,7 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
test(
|
test(
|
||||||
'ensure you can not export while an export is already going',
|
'ensure you can not export while an export is already going',
|
||||||
{ tag: ['@skipLinux', '@skipWin'] },
|
{ tag: ['@skipLinux', '@skipWin'] },
|
||||||
async ({ page }) => {
|
async ({ page, homePage }) => {
|
||||||
// This is being weird on ubuntu and windows.
|
// This is being weird on ubuntu and windows.
|
||||||
test.skip(
|
test.skip(
|
||||||
// eslint-disable-next-line jest/valid-title
|
// 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
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -500,20 +517,17 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
test(
|
test(
|
||||||
`Network health indicator only appears in modeling view`,
|
`Network health indicator only appears in modeling view`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName: _ }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await context.folderSetupFn(async (dir) => {
|
||||||
testInfo,
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
folderSetupFn: async (dir) => {
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
const bracketDir = join(dir, 'bracket')
|
await fsp.copyFile(
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
await fsp.copyFile(
|
path.join(bracketDir, 'main.kcl')
|
||||||
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 u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Locators
|
// Locators
|
||||||
@ -539,18 +553,19 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
await expect(networkHealthIndicator).toContainText('Connected')
|
await expect(networkHealthIndicator).toContainText('Connected')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(`View gizmo stays visible even when zoomed out all the way`, async ({
|
test(`View gizmo stays visible even when zoomed out all the way`, async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Constants and locators
|
// 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 bgColor: [number, number, number] = [27, 27, 27]
|
||||||
const middlePixelIsColor = async (color: [number, number, number]) => {
|
const middlePixelIsColor = async (color: [number, number, number]) => {
|
||||||
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
||||||
@ -561,8 +576,9 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem('persistCode', '')
|
localStorage.setItem('persistCode', '')
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -572,7 +588,7 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
message: 'Plane color is visible',
|
message: 'Plane color is visible',
|
||||||
})
|
})
|
||||||
.toBeLessThan(15)
|
.toBeLessThanOrEqual(15)
|
||||||
|
|
||||||
let maxZoomOuts = 10
|
let maxZoomOuts = 10
|
||||||
let middlePixelIsBackgroundColor =
|
let middlePixelIsBackgroundColor =
|
||||||
@ -590,7 +606,7 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(middlePixelIsBackgroundColor, {
|
expect(middlePixelIsBackgroundColor, {
|
||||||
message: 'We no longer the default planes',
|
message: 'We should not see the default planes',
|
||||||
}).toBeTruthy()
|
}).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -598,6 +614,38 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
await expect(gizmo).toBeVisible()
|
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) {
|
async function clickExportButton(page: Page) {
|
||||||
|
@ -47,7 +47,11 @@ test.beforeEach(async ({ page }) => {
|
|||||||
|
|
||||||
test.setTimeout(60_000)
|
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',
|
'exports of each format should work',
|
||||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
||||||
async ({ page, context }) => {
|
async ({ page, context }) => {
|
||||||
@ -371,6 +375,7 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|
|||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
}
|
}
|
||||||
@ -950,7 +955,75 @@ test(
|
|||||||
|
|
||||||
test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||||
// FIXME: Skip on macos its being weird.
|
// 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 }) => {
|
test('Grid turned off', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -1096,3 +1169,109 @@ test.fixme('theme persists', async ({ page, context }) => {
|
|||||||
maxDiffPixels: 100,
|
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 |