Compare commits
136 Commits
achalmers/
...
v0.34.0
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
ff2103d493 | |||
2dfa8f2176 | |||
29ed330326 | |||
ca2cc825a6 | |||
83fe1b7ce0 | |||
157b76cc78 | |||
cf957d880e | |||
dfc3d19677 | |||
dd370a9365 | |||
2274d6459c | |||
32ce857119 | |||
88b51da417 | |||
30d365aeb3 | |||
7af62399ac | |||
441d957228 | |||
9e57034873 | |||
eb96d6539c | |||
513c76ecc8 | |||
51d9449280 | |||
6366bc4766 | |||
7a21918223 | |||
8072f1db63 | |||
18e1855fa9 | |||
7be53c7d4a | |||
2bf20988ef | |||
1495cc6d18 | |||
f876e6ca3c | |||
60a0c811ab | |||
cab0c1e6a1 | |||
417d720b22 | |||
77293952c0 | |||
ea3d604b73 | |||
023a659491 | |||
dd3a2b14f9 | |||
424b409cc1 | |||
82a58e69c2 | |||
776b420031 | |||
1087d4223b | |||
089d6df889 | |||
efb067af58 | |||
2aa27eab01 | |||
9c47ac5b57 | |||
5ae1aecd74 | |||
68ae7e98f9 | |||
56771d561a | |||
f09411817c | |||
bed7ae3b8b | |||
c43510732c | |||
51f0b669a4 | |||
3cbedcd3e7 | |||
5d2fa43150 | |||
ec49b0752e | |||
3b171fb881 | |||
0548409da0 | |||
dd052b35fd | |||
46be4e7eef | |||
412d1b7a99 | |||
cfdd22af74 | |||
68a11e7aa5 | |||
3139e18dc7 |
@ -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
|
|
20
.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
|
||||||
@ -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."
|
||||||
|
35
.github/workflows/build-apps.yml
vendored
@ -165,7 +165,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,7 +172,6 @@ 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
|
run: yarn electron-builder --config --publish always
|
||||||
|
|
||||||
@ -229,7 +227,6 @@ 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
|
run: yarn electron-builder --config --publish always
|
||||||
|
|
||||||
@ -362,6 +359,17 @@ jobs:
|
|||||||
- name: List artifacts
|
- name: List artifacts
|
||||||
run: "ls -R out"
|
run: "ls -R out"
|
||||||
|
|
||||||
|
- name: Set more complete nightly release notes
|
||||||
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
|
run: |
|
||||||
|
# Note: preferred going this way instead of a full clone in the checkout step,
|
||||||
|
# see https://github.com/actions/checkout/issues/1471
|
||||||
|
git fetch --prune --unshallow --tags
|
||||||
|
export TAG="nightly-${VERSION}"
|
||||||
|
export PREVIOUS_TAG=$(git describe --tags --match="nightly-v[0-9]*" --abbrev=0)
|
||||||
|
export NOTES=$(./scripts/get-nightly-changelog.sh)
|
||||||
|
yarn files:set-notes
|
||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
- name: Authenticate to Google Cloud
|
||||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
uses: 'google-github-actions/auth@v2.1.7'
|
uses: 'google-github-actions/auth@v2.1.7'
|
||||||
@ -383,12 +391,17 @@ jobs:
|
|||||||
parent: false
|
parent: false
|
||||||
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
|
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||||
uses: softprops/action-gh-release@v2
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
if: ${{ env.IS_RELEASE == 'true' }}
|
run: yarn files:invalidate-bucket:nightly
|
||||||
|
|
||||||
|
- name: Tag nightly commit
|
||||||
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.VERSION }}
|
script: |
|
||||||
tag_name: ${{ env.VERSION }}
|
const { VERSION } = process.env
|
||||||
draft: true
|
const { owner, repo } = context.repo
|
||||||
generate_release_notes: true
|
const { sha } = context
|
||||||
files: 'out/Zoo*'
|
const ref = `refs/tags/nightly-${VERSION}`
|
||||||
|
github.rest.git.createRef({ owner, repo, sha, ref })
|
||||||
|
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
|
||||||
|
166
.github/workflows/e2e-tests.yml
vendored
@ -18,6 +18,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
check-rust-changes:
|
check-rust-changes:
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
rust-changed: ${{ steps.filter.outputs.rust }}
|
rust-changed: ${{ steps.filter.outputs.rust }}
|
||||||
@ -33,20 +34,20 @@ jobs:
|
|||||||
rust:
|
rust:
|
||||||
- 'src/wasm-lib/**'
|
- 'src/wasm-lib/**'
|
||||||
|
|
||||||
browser:
|
electron:
|
||||||
timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }}
|
if: github.event.pull_request.draft == false
|
||||||
name: playwright:browser:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
timeout-minutes: 60
|
||||||
|
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:
|
||||||
@ -68,7 +69,7 @@ jobs:
|
|||||||
- name: Download Wasm Cache
|
- name: Download Wasm Cache
|
||||||
id: download-wasm
|
id: download-wasm
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@v7
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
@ -101,7 +102,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
|
||||||
|
if: false
|
||||||
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
|
||||||
@ -123,13 +125,13 @@ jobs:
|
|||||||
if: steps.download-wasm.outcome == 'failure'
|
if: steps.download-wasm.outcome == 'failure'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: yarn build:wasm
|
run: yarn build:wasm
|
||||||
- name: build web
|
- name: build electron
|
||||||
run: yarn build:local
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
run: yarn tron:package
|
||||||
- name: Run ubuntu/chrome snapshots
|
- name: Run ubuntu/chrome snapshots
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
@ -186,12 +188,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
- name: Run playwright/chrome flow (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-browser-chrome.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{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
|
||||||
@ -199,11 +201,6 @@ jobs:
|
|||||||
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
|
||||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
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
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
@ -221,136 +218,3 @@ jobs:
|
|||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
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@v6
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
name: wasm-bundle
|
|
||||||
workflow: build-and-store-wasm.yml
|
|
||||||
branch: main
|
|
||||||
path: src/wasm-lib/pkg
|
|
||||||
- name: copy wasm blob
|
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
|
||||||
shell: bash
|
|
||||||
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
|
||||||
continue-on-error: true
|
|
||||||
- name: Setup Rust
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
- name: Cache Wasm (because rust diff)
|
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: './src/wasm-lib'
|
|
||||||
- name: OR Cache Wasm (because wasm cache failed)
|
|
||||||
if: steps.download-wasm.outcome == 'failure'
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: './src/wasm-lib'
|
|
||||||
- name: install good sed
|
|
||||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
brew install gnu-sed
|
|
||||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
|
||||||
- name: Install vector
|
|
||||||
if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
|
||||||
chmod +x /tmp/vector.sh
|
|
||||||
/tmp/vector.sh -y -no-modify-path
|
|
||||||
mkdir -p /tmp/vector
|
|
||||||
cp .github/workflows/vector.toml /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
|
|
||||||
cat /tmp/vector.toml
|
|
||||||
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
|
|
||||||
- name: Build Wasm (because rust diff)
|
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
|
||||||
shell: bash
|
|
||||||
run: yarn build:wasm
|
|
||||||
- name: OR Build Wasm (because wasm cache failed)
|
|
||||||
if: steps.download-wasm.outcome == 'failure'
|
|
||||||
shell: bash
|
|
||||||
run: yarn build:wasm
|
|
||||||
- name: build electron
|
|
||||||
shell: bash
|
|
||||||
run: yarn tron:package
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
|
||||||
path: test-results/
|
|
||||||
- name: Run electron tests (with retries)
|
|
||||||
id: retry
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
.github/ci-cd-scripts/playwright-electron.sh ${{ matrix.os }}
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
FAIL_ON_CONSOLE_ERRORS: true
|
|
||||||
NODE_ENV: development
|
|
||||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
|
||||||
VITE_KC_SKIP_AUTH: true
|
|
||||||
IS_UBUNTU: ${{ startsWith(matrix.os, 'ubuntu') && 'true' || 'false' }}
|
|
||||||
#DEBUG: 'pw:browser*'
|
|
||||||
- name: send to axiom
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) && !startsWith(matrix.os, 'windows') }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
node playwrightProcess.mjs | tee /tmp/github-actions.log
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
|
||||||
with:
|
|
||||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
|
||||||
path: test-results/
|
|
||||||
include-hidden-files: true
|
|
||||||
retention-days: 30
|
|
||||||
overwrite: true
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
|
||||||
with:
|
|
||||||
name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }}
|
|
||||||
path: playwright-report/
|
|
||||||
include-hidden-files: true
|
|
||||||
retention-days: 30
|
|
||||||
overwrite: true
|
|
||||||
|
12
.github/workflows/publish-apps-release.yml
vendored
@ -126,11 +126,13 @@ jobs:
|
|||||||
destination: 'dl.kittycad.io/releases/modeling-app'
|
destination: 'dl.kittycad.io/releases/modeling-app'
|
||||||
|
|
||||||
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||||
run: |
|
run: yarn files:invalidate-bucket
|
||||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/last_download.json" --async
|
|
||||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-linux-arm64.yml" --async
|
- name: Upload release files to Github
|
||||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async
|
if: ${{ github.event_name == 'release' }}
|
||||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: 'out/Zoo*'
|
||||||
|
|
||||||
|
|
||||||
announce_release:
|
announce_release:
|
||||||
|
1
.gitignore
vendored
@ -61,6 +61,7 @@ Mac_App_Distribution.provisionprofile
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
src/wasm-lib/pkg
|
src/wasm-lib/pkg
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
venv
|
venv
|
||||||
.vite/
|
.vite/
|
||||||
|
|
||||||
|
43
INSTALL.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Setting Up Zoo Modeling App
|
||||||
|
|
||||||
|
Compared to other CAD software, getting Zoo Modeling App up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install.
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
|
||||||
|
|
||||||
|
2. Once downloaded, run the installer `Zoo Modeling App-{version}-{arch}-win.exe` which should take a few seconds.
|
||||||
|
|
||||||
|
3. The installation happens at `C:\Program Files\Zoo Modeling App`. A shortcut in the start menu is also created so you can run the app easily by clicking on it.
|
||||||
|
|
||||||
|
## macOS
|
||||||
|
|
||||||
|
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
|
||||||
|
|
||||||
|
2. Once downloaded, open the disk image `Zoo Modeling App-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
|
||||||
|
|
||||||
|
3. You can then open your `Applications` directory and double-click on `Zoo Modeling App` to open.
|
||||||
|
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
|
||||||
|
|
||||||
|
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
|
||||||
|
- On Ubuntu, install the FUSE library with these commands in a terminal.
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libfuse2
|
||||||
|
```
|
||||||
|
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
|
||||||
|
- Once installed, copy the downloaded `Zoo Modeling App-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
|
||||||
|
|
||||||
|
- `appimaged` should automatically find it and make it executable. If not, run:
|
||||||
|
```bash
|
||||||
|
chmod a+x ~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
|
||||||
|
```
|
||||||
|
|
||||||
|
3. You can double-click on the AppImage to run it, or in a terminal with this command:
|
||||||
|
```bash
|
||||||
|
~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
|
||||||
|
```
|
39
README.md
@ -99,7 +99,7 @@ yarn tron:start
|
|||||||
|
|
||||||
This will start the application and hot-reload on changes.
|
This will start the application and hot-reload on changes.
|
||||||
|
|
||||||
Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
|
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
|
||||||
|
|
||||||
To build, run `yarn tron:package`.
|
To build, run `yarn tron:package`.
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ https://github.com/KittyCAD/modeling-app/issues/new
|
|||||||
|
|
||||||
#### 2. Push a new tag
|
#### 2. Push a new tag
|
||||||
|
|
||||||
Create a new tag and push it to the repo (eg. `v0.28.0` for `$VERSION`)
|
Create a new tag and push it to the repo. The `semantic-release.sh` script will automatically bump the minor part, which we use the most. For instance going from `v0.27.0` to `v0.28.0`.
|
||||||
|
|
||||||
```
|
```
|
||||||
VERSION=$(./scripts/semantic-release.sh)
|
VERSION=$(./scripts/semantic-release.sh)
|
||||||
@ -146,16 +146,14 @@ git push origin --tags
|
|||||||
|
|
||||||
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files as well as updater-test artifacts.
|
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files as well as updater-test artifacts.
|
||||||
|
|
||||||
Once the workflow succeeds, a draft release will be created at https://github.com/KittyCAD/modeling-app/releases.
|
The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush)).
|
||||||
|
|
||||||
#### 3. Manually test artifacts from the Cut Release PR
|
#### 3. Manually test artifacts
|
||||||
|
|
||||||
##### Release builds
|
##### Release builds
|
||||||
|
|
||||||
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in 2.).
|
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in 2.).
|
||||||
|
|
||||||
Alternatively, the draft release will also include these builds.
|
|
||||||
|
|
||||||
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
|
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
|
||||||
|
|
||||||
##### Updater-test builds
|
##### Updater-test builds
|
||||||
@ -178,9 +176,11 @@ If the prompt doesn't show up, start the app in command line to grab the electro
|
|||||||
|
|
||||||
#### 4. Publish the release
|
#### 4. Publish the release
|
||||||
|
|
||||||
Head over to https://github.com/KittyCAD/modeling-app/releases, paste in the changelog discussed in the issue, and publish the draft release created by the `build-apps` workflow from step 2.
|
Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the _Release title_ field as well.
|
||||||
|
|
||||||
A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter. On success, the files will be uploaded to the public bucket and the announcement on Discord will be sent.
|
Hit _Generate release notes_ as a starting point to discuss the changelog in the issue. Once done, make sure _Set as the latest release_ is checked, and hit _Publish release_.
|
||||||
|
|
||||||
|
A new `publish-apps-release` will kick in and you should be able to find it [here](https://github.com/KittyCAD/modeling-app/actions?query=event%3Arelease). On success, the files will be uploaded to the public bucket as well as to the GitHub release, and the announcement on Discord will be sent.
|
||||||
|
|
||||||
#### 5. Close the issue
|
#### 5. Close the issue
|
||||||
|
|
||||||
@ -388,23 +388,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.
|
||||||
@ -450,3 +433,9 @@ PS: for the debug panel, the following JSON is useful for snapping the camera
|
|||||||
## KCL
|
## KCL
|
||||||
|
|
||||||
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
To display logging (to the terminal or console) set `ZOO_LOG=1`. This will log some warnings and simple performance metrics. To view these in test runs, use `-- --nocapture`.
|
||||||
|
|
||||||
|
To enable memory metrics, build with `--features dhat-heap`.
|
||||||
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 259 KiB |
BIN
assets/icon.ico
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 114 KiB |
BIN
assets/icon.png
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
@ -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
@ -58,7 +58,7 @@ mountingPlate = extrude(thickness, mountingPlateSketch)
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Sketch on the face of a chamfer.
|
// Sketch on the face of a chamfer.
|
||||||
fn cube = (pos, scale) => {
|
fn cube(pos, scale) {
|
||||||
sg = startSketchOn('XY')
|
sg = startSketchOn('XY')
|
||||||
|> startProfileAt(pos, %)
|
|> startProfileAt(pos, %)
|
||||||
|> line([0, scale], %)
|
|> line([0, scale], %)
|
||||||
|
@ -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,6 +30,7 @@ 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)
|
||||||
@ -99,8 +101,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)
|
||||||
|
@ -37,7 +37,7 @@ assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
|||||||
startSketchOn('XZ')
|
startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle({ center = [0, 0], radius = 2 }, %)
|
||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|> patternTransform(n, (id) => {
|
|> patternTransform(n, fn(id) {
|
||||||
return { translate = [4 * id, 0, 0] }
|
return { translate = [4 * id, 0, 0] }
|
||||||
}, %)
|
}, %)
|
||||||
```
|
```
|
||||||
|
@ -29,7 +29,7 @@ map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
r = 10 // radius
|
r = 10 // radius
|
||||||
fn drawCircle = (id) => {
|
fn drawCircle(id) {
|
||||||
return startSketchOn("XY")
|
return startSketchOn("XY")
|
||||||
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
||||||
}
|
}
|
||||||
@ -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 }, %)
|
||||||
})
|
})
|
||||||
|
@ -12,7 +12,7 @@ to other modules.
|
|||||||
|
|
||||||
```
|
```
|
||||||
// util.kcl
|
// util.kcl
|
||||||
export fn increment = (x) => {
|
export fn increment(x) {
|
||||||
return x + 1
|
return x + 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -37,11 +37,11 @@ Multiple functions can be exported in a file.
|
|||||||
|
|
||||||
```
|
```
|
||||||
// util.kcl
|
// util.kcl
|
||||||
export fn increment = (x) => {
|
export fn increment(x) {
|
||||||
return x + 1
|
return x + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn decrement = (x) => {
|
export fn decrement(x) {
|
||||||
return x - 1
|
return x - 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -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 |
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ patternTransform2d(total_instances: u32, transform_function: FunctionParam, soli
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Each instance will be shifted along the X axis.
|
// Each instance will be shifted along the X axis.
|
||||||
fn transform = (id) => {
|
fn transform(id) {
|
||||||
return { translate = [4 * id, 0] }
|
return { translate = [4 * id, 0] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,20 +30,20 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// This function adds two numbers.
|
// This function adds two numbers.
|
||||||
fn add = (a, b) => {
|
fn add(a, b) {
|
||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function adds an array of numbers.
|
// This function adds an array of numbers.
|
||||||
// It uses the `reduce` function, to call the `add` function on every
|
// It uses the `reduce` function, to call the `add` function on every
|
||||||
// element of the `arr` parameter. The starting value is 0.
|
// element of the `arr` parameter. The starting value is 0.
|
||||||
fn sum = (arr) => {
|
fn sum(arr) {
|
||||||
return reduce(arr, 0, add)
|
return reduce(arr, 0, add)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -74,17 +74,18 @@ assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Declare a function that sketches a decagon.
|
// Declare a function that sketches a decagon.
|
||||||
fn decagon = (radius) => {
|
fn decagon(radius) {
|
||||||
// Each side of the decagon is turned this many degrees from the previous angle.
|
// Each side of the decagon is turned this many degrees from the previous angle.
|
||||||
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)
|
||||||
@ -36,8 +37,9 @@ cube = startSketchAt([0, 0])
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> 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)
|
||||||
@ -36,8 +37,9 @@ cube = startSketchAt([0, 0])
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> 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.
|
||||||
|
|
||||||
|
|
||||||
|
85480
docs/kcl/std.json
55
docs/kcl/sweep.md
Normal file
@ -41,7 +41,7 @@ If you want to get a value from an array you can use the index like so:
|
|||||||
An object is defined with `{}` braces. Here is an example object:
|
An object is defined with `{}` braces. Here is an example object:
|
||||||
|
|
||||||
```
|
```
|
||||||
myObj = {a: 0, b: "thing"}
|
myObj = { a = 0, b = "thing" }
|
||||||
```
|
```
|
||||||
|
|
||||||
We support two different ways of getting properties from objects, you can call
|
We support two different ways of getting properties from objects, you can call
|
||||||
@ -54,7 +54,7 @@ We also have support for defining your own functions. Functions can take in any
|
|||||||
type of argument. Below is an example of the syntax:
|
type of argument. Below is an example of the syntax:
|
||||||
|
|
||||||
```
|
```
|
||||||
fn myFn = (x) => {
|
fn myFn(x) {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -90,12 +90,12 @@ startSketchOn('XZ')
|
|||||||
|> startProfileAt(origin, %)
|
|> startProfileAt(origin, %)
|
||||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %) - 90,
|
segAng(rectangleSegmentA001) - 90,
|
||||||
196.99
|
196.99
|
||||||
], %, $rectangleSegmentB001)
|
], %, $rectangleSegmentB001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001, %)
|
-segLen(rectangleSegmentA001)
|
||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
@ -118,20 +118,20 @@ use the tag `rectangleSegmentA001` in any function or expression in the file.
|
|||||||
However if the code was written like this:
|
However if the code was written like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
fn rect = (origin) => {
|
fn rect(origin) {
|
||||||
return startSketchOn('XZ')
|
return startSketchOn('XZ')
|
||||||
|> startProfileAt(origin, %)
|
|> startProfileAt(origin, %)
|
||||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %) - 90,
|
segAng(rectangleSegmentA001) - 90,
|
||||||
196.99
|
196.99
|
||||||
], %, $rectangleSegmentB001)
|
], %, $rectangleSegmentB001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001, %)
|
-segLen(rectangleSegmentA001)
|
||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
}
|
}
|
||||||
|
|
||||||
rect([0, 0])
|
rect([0, 0])
|
||||||
@ -146,28 +146,31 @@ Tags are accessible through the sketch group they are declared in.
|
|||||||
For example the following code works.
|
For example the following code works.
|
||||||
|
|
||||||
```
|
```
|
||||||
fn rect = (origin) => {
|
fn rect(origin) {
|
||||||
return startSketchOn('XZ')
|
return startSketchOn('XZ')
|
||||||
|> startProfileAt(origin, %)
|
|> startProfileAt(origin, %)
|
||||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %) - 90,
|
segAng(rectangleSegmentA001) - 90,
|
||||||
196.99
|
196.99
|
||||||
], %, $rectangleSegmentB001)
|
], %, $rectangleSegmentB001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001, %)
|
-segLen(rectangleSegmentA001)
|
||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
}
|
}
|
||||||
|
|
||||||
rect([0, 0])
|
rect([0, 0])
|
||||||
myRect = rect([20, 0])
|
myRect = rect([20, 0])
|
||||||
|
|
||||||
myRect
|
myRect
|
||||||
|> extrude(10, %)
|
|> extrude(10, %)
|
||||||
|> fillet({radius: 0.5, tags: [myRect.tags.rectangleSegmentA001]}, %)
|
|> fillet({
|
||||||
|
radius = 0.5,
|
||||||
|
tags = [myRect.tags.rectangleSegmentA001]
|
||||||
|
}, %)
|
||||||
```
|
```
|
||||||
|
|
||||||
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside
|
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside
|
||||||
|
@ -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,161 +0,0 @@
|
|||||||
---
|
|
||||||
title: "BinaryOperator"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
Add two numbers.
|
|
||||||
|
|
||||||
**enum:** `+`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Subtract two numbers.
|
|
||||||
|
|
||||||
**enum:** `-`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Multiply two numbers.
|
|
||||||
|
|
||||||
**enum:** `*`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Divide two numbers.
|
|
||||||
|
|
||||||
**enum:** `/`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Modulo two numbers.
|
|
||||||
|
|
||||||
**enum:** `%`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Raise a number to a power.
|
|
||||||
|
|
||||||
**enum:** `^`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Are two numbers equal?
|
|
||||||
|
|
||||||
**enum:** `==`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Are two numbers not equal?
|
|
||||||
|
|
||||||
**enum:** `!=`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Is left greater than right
|
|
||||||
|
|
||||||
**enum:** `>`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Is left greater than or equal to right
|
|
||||||
|
|
||||||
**enum:** `>=`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Is left less than right
|
|
||||||
|
|
||||||
**enum:** `<`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Is left less than or equal to right
|
|
||||||
|
|
||||||
**enum:** `<=`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
|||||||
---
|
|
||||||
title: "BinaryPart"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `Literal`| | No |
|
|
||||||
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
|
|
||||||
| `raw` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
|
||||||
| `name` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `BinaryExpression`| | No |
|
|
||||||
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| | No |
|
|
||||||
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
|
|
||||||
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `CallExpression`| | No |
|
|
||||||
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
|
|
||||||
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
|
||||||
| `optional` |`boolean`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `UnaryExpression`| | No |
|
|
||||||
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| | No |
|
|
||||||
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `MemberExpression`| | No |
|
|
||||||
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
|
|
||||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
|
|
||||||
| `computed` |`boolean`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `IfExpression`| | No |
|
|
||||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
|
||||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
|
|
||||||
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
|
|
||||||
| `final_else` |[`Program`](/docs/kcl/types/Program)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
|||||||
---
|
|
||||||
title: "BodyItem"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `ImportStatement`| | No |
|
|
||||||
| `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
|
|
||||||
| `path` |`string`| | No |
|
|
||||||
| `raw_path` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `ExpressionStatement`| | No |
|
|
||||||
| `expression` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `VariableDeclaration`| | No |
|
|
||||||
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | No |
|
|
||||||
| `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)| | No |
|
|
||||||
| `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `ReturnStatement`| | No |
|
|
||||||
| `argument` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
|||||||
---
|
|
||||||
title: "CommentStyle"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
Like // foo
|
|
||||||
|
|
||||||
**enum:** `line`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Like /* foo */
|
|
||||||
|
|
||||||
**enum:** `block`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: "ElseIf"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
|
||||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
title: "EnvironmentRef"
|
|
||||||
excerpt: "An index pointing to an environment."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
An index pointing to an environment.
|
|
||||||
|
|
||||||
**Type:** `integer` (`uint`)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,318 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Expr"
|
|
||||||
excerpt: "An expression can be evaluated to yield a single KCL value."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
An expression can be evaluated to yield a single KCL value.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `Literal`| | No |
|
|
||||||
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `raw` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
|
||||||
| `name` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
|
|
||||||
| `value` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `BinaryExpression`| | No |
|
|
||||||
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)| | No |
|
|
||||||
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
|
|
||||||
| `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `CallExpression`| | No |
|
|
||||||
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
|
||||||
| `optional` |`boolean`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `PipeExpression`| | No |
|
|
||||||
| `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
|
||||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `PipeSubstitution`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `ArrayExpression`| | No |
|
|
||||||
| `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
|
||||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `ArrayRangeExpression`| | No |
|
|
||||||
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `ObjectExpression`| | No |
|
|
||||||
| `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`| | No |
|
|
||||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `MemberExpression`| | No |
|
|
||||||
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `computed` |`boolean`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `UnaryExpression`| | No |
|
|
||||||
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `IfExpression`| | No |
|
|
||||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
|
|
||||||
| `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `None`| | No |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: "FunctionExpression"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
|
|
||||||
| `body` |[`Program`](/docs/kcl/types/Program)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Identifier"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `name` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: "ImportItem"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No |
|
|
||||||
| `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
title: "ItemVisibility"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**enum:** `default`, `export`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
@ -317,7 +317,6 @@ Data for an imported geometry.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Function`| | No |
|
| `type` |enum: `Function`| | No |
|
||||||
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No |
|
|
||||||
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
|
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
@ -330,6 +329,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 |
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
---
|
|
||||||
title: "LiteralIdentifier"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
|
||||||
| `name` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `Literal`| | No |
|
|
||||||
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
|
|
||||||
| `raw` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
title: "LiteralValue"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts any of the following:**
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `number` (`double`)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `string`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `boolean`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
|||||||
---
|
|
||||||
title: "MemberObject"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `MemberExpression`| | No |
|
|
||||||
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
|
|
||||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
|
|
||||||
| `computed` |`boolean`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
|
||||||
| `name` |`string`| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | 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`)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
title: "NonCodeMeta"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `nonCodeNodes` |`object`| | No |
|
|
||||||
| `startNodes` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | No |
|
|
||||||
| `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 |
|
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
title: "NonCodeNode"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
|||||||
---
|
|
||||||
title: "NonCodeValue"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `inlineComment`| | No |
|
|
||||||
| `value` |`string`| | No |
|
|
||||||
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
A block comment. An example of this is the following: ```python,no_run /* This is a block comment */ 1 + 1 ``` Now this is important. The block comment is attached to the next line. This is always the case. Also the block comment doesn't have a new line above it. If it did it would be a `NewLineBlockComment`.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `blockComment`| | No |
|
|
||||||
| `value` |`string`| | No |
|
|
||||||
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
A block comment that has a new line above it. The user explicitly added a new line above the block comment.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `newLineBlockComment`| | No |
|
|
||||||
| `value` |`string`| | No |
|
|
||||||
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: `newLine`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: "ObjectProperty"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `key` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
|
|
||||||
| `value` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Parameter"
|
|
||||||
excerpt: "Parameter of a KCL function."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
Parameter of a KCL function.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `identifier` |[`Identifier`](/docs/kcl/types/Identifier)| The parameter's label or name. | No |
|
|
||||||
| `optional` |`boolean`| Is the parameter optional? | No |
|
|
||||||
| `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 |
|
|
||||||
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Program"
|
|
||||||
excerpt: "A KCL program top level, or function body."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
A KCL program top level, or function body.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No |
|
|
||||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No |
|
|
||||||
| `shebang` |[`Shebang`](/docs/kcl/types/Shebang)| | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Shebang"
|
|
||||||
excerpt: "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```"
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `content` |`string`| | No |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | 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` |[`Sketch`](/docs/kcl/types/Sketch)| 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 |
|
||||||
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Uint"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `integer` (`uint32`)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
|||||||
---
|
|
||||||
title: "UnaryOperator"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
Negate a number.
|
|
||||||
|
|
||||||
**enum:** `-`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Negate a boolean.
|
|
||||||
|
|
||||||
**enum:** `!`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: "VariableDeclarator"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No |
|
|
||||||
| `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No |
|
|
||||||
| `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 |
|
|
||||||
| `start` |`integer`| | No |
|
|
||||||
| `end` |`integer`| | No |
|
|
||||||
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
|||||||
---
|
|
||||||
title: "VariableKind"
|
|
||||||
excerpt: ""
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
Declare a named constant.
|
|
||||||
|
|
||||||
**enum:** `const`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Declare a function.
|
|
||||||
|
|
||||||
**enum:** `fn`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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.fixme('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()
|
||||||
@ -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,30 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Command bar tests', () => {
|
test.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,24 +45,25 @@ test.describe('Command bar tests', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Fillet from command bar', async ({ page }) => {
|
// TODO: fix this test after the electron migration
|
||||||
|
test.fixme('Fillet from command bar', async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XY')
|
`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-5, -5], %)
|
|> startProfileAt([-5, -5], %)
|
||||||
|> line([0, 10], %)
|
|> line([0, 10], %)
|
||||||
|> line([10, 0], %)
|
|> line([10, 0], %)
|
||||||
|> line([0, -10], %)
|
|> line([0, -10], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(-10, sketch001)`
|
extrude001 = extrude(-10, sketch001)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
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.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -93,10 +87,10 @@ extrude001 = extrude(-10, sketch001)`
|
|||||||
|
|
||||||
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 +147,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 +168,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 +215,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 +287,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' })
|
||||||
|
@ -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,7 +32,7 @@ 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.
|
||||||
|
@ -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.
|
||||||
const topAng = 30
|
* hopefully ~ doesn't change meaning
|
||||||
const 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')
|
||||||
@ -479,10 +587,12 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
// error text on hover
|
// error text on hover
|
||||||
await page.hover('.cm-lint-marker-error')
|
await page.hover('.cm-lint-marker-error')
|
||||||
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
|
await expect(
|
||||||
|
page.getByText("found unknown token '~'").first()
|
||||||
|
).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')
|
||||||
@ -518,103 +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()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => {
|
test.fixme(
|
||||||
const u = await getUtils(page)
|
'error with 2 source ranges gets 2 diagnostics',
|
||||||
await page.addInitScript(async () => {
|
async ({ page, homePage }) => {
|
||||||
localStorage.setItem(
|
const u = await getUtils(page)
|
||||||
'persistCode',
|
await page.addInitScript(async () => {
|
||||||
`length = .750
|
localStorage.setItem(
|
||||||
width = 0.500
|
'persistCode',
|
||||||
height = 0.500
|
`length = .750
|
||||||
dia = 4
|
width = 0.500
|
||||||
|
height = 0.500
|
||||||
|
dia = 4
|
||||||
|
|
||||||
|
fn squareHole = (l, w) => {
|
||||||
|
squareHoleSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-width / 2, -length / 2], %)
|
||||||
|
|> lineTo([width / 2, -length / 2], %)
|
||||||
|
|> lineTo([width / 2, length / 2], %)
|
||||||
|
|> lineTo([-width / 2, length / 2], %)
|
||||||
|
|> close(%)
|
||||||
|
return squareHoleSketch
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.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()
|
||||||
|
|
||||||
@ -625,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
|
||||||
@ -692,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.
|
||||||
@ -766,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()
|
||||||
@ -838,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()
|
||||||
@ -887,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
|
||||||
|
|
||||||
@ -901,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)
|
||||||
@ -941,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')
|
||||||
@ -955,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')
|
||||||
@ -968,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')
|
||||||
@ -983,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)
|
||||||
@ -1065,8 +1188,6 @@ test.describe('Editor tests', () => {
|
|||||||
})
|
})
|
||||||
.toBeGreaterThan(15)
|
.toBeGreaterThan(15)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -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,20 @@ 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))
|
|
||||||
},
|
|
||||||
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) => {
|
||||||
await use(new EditorFixture(page))
|
await use(new EditorFixture(page))
|
||||||
},
|
},
|
||||||
toolbar: async ({ page }, use) => {
|
toolbar: async ({ page }: { page: Page }, use: any) => {
|
||||||
await use(new ToolbarFixture(page))
|
await use(new ToolbarFixture(page))
|
||||||
},
|
},
|
||||||
scene: async ({ page }, use) => {
|
scene: async ({ page }: { page: Page }, use: any) => {
|
||||||
await use(new SceneFixture(page))
|
await use(new SceneFixture(page))
|
||||||
},
|
},
|
||||||
homePage: async ({ page }, use) => {
|
homePage: async ({ page }: { page: Page }, use: any) => {
|
||||||
await use(new HomePageFixture(page))
|
await use(new HomePageFixture(page))
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
export { expect } from '@playwright/test'
|
|
||||||
|
@ -4,7 +4,6 @@ import { expect } from '@playwright/test'
|
|||||||
interface ProjectCardState {
|
interface ProjectCardState {
|
||||||
title: string
|
title: string
|
||||||
fileCount: number
|
fileCount: number
|
||||||
folderCount: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HomePageState {
|
interface HomePageState {
|
||||||
@ -15,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
|
||||||
|
|
||||||
@ -29,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')
|
||||||
}
|
}
|
||||||
@ -61,15 +72,13 @@ export class HomePageFixture {
|
|||||||
const projectCards = await this.projectCard.all()
|
const projectCards = await this.projectCard.all()
|
||||||
const projectCardStates: Array<ProjectCardState> = []
|
const projectCardStates: Array<ProjectCardState> = []
|
||||||
for (const projectCard of projectCards) {
|
for (const projectCard of projectCards) {
|
||||||
const [title, fileCount, folderCount] = await Promise.all([
|
const [title, fileCount] = await Promise.all([
|
||||||
(await projectCard.locator(this.projectCardTitle).textContent()) || '',
|
(await projectCard.locator(this.projectCardTitle).textContent()) || '',
|
||||||
Number(await projectCard.locator(this.projectCardFile).textContent()),
|
Number(await projectCard.locator(this.projectCardFile).textContent()),
|
||||||
Number(await projectCard.locator(this.projectCardFolder).textContent()),
|
|
||||||
])
|
])
|
||||||
projectCardStates.push({
|
projectCardStates.push({
|
||||||
title: title,
|
title: title,
|
||||||
fileCount,
|
fileCount,
|
||||||
folderCount,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return projectCardStates
|
return projectCardStates
|
||||||
@ -94,10 +103,25 @@ export class HomePageFixture {
|
|||||||
.toEqual(expectedState)
|
.toEqual(expectedState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createAndGoToProject = async (projectTitle: string) => {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ type SceneSerialised = {
|
|||||||
|
|
||||||
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
||||||
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
|
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
|
||||||
|
type DblClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
||||||
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
|
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
|
||||||
type DragFromHandler = (
|
type DragFromHandler = (
|
||||||
dragParams: mouseDragFromParams
|
dragParams: mouseDragFromParams
|
||||||
@ -52,8 +53,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)
|
||||||
}
|
}
|
||||||
@ -68,7 +70,7 @@ export class SceneFixture {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
{ steps }: { steps: number } = { steps: 20 }
|
{ steps }: { steps: number } = { steps: 20 }
|
||||||
): [ClickHandler, MoveHandler] =>
|
): [ClickHandler, MoveHandler, DblClickHandler] =>
|
||||||
[
|
[
|
||||||
(clickParams?: mouseParams) => {
|
(clickParams?: mouseParams) => {
|
||||||
if (clickParams?.pixelDiff) {
|
if (clickParams?.pixelDiff) {
|
||||||
@ -90,6 +92,16 @@ export class SceneFixture {
|
|||||||
}
|
}
|
||||||
return this.page.mouse.move(x, y, { steps })
|
return this.page.mouse.move(x, y, { steps })
|
||||||
},
|
},
|
||||||
|
(clickParams?: mouseParams) => {
|
||||||
|
if (clickParams?.pixelDiff) {
|
||||||
|
return doAndWaitForImageDiff(
|
||||||
|
this.page,
|
||||||
|
() => this.page.mouse.dblclick(x, y),
|
||||||
|
clickParams.pixelDiff
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.page.mouse.dblclick(x, y)
|
||||||
|
},
|
||||||
] as const
|
] as const
|
||||||
makeDragHelpers = (
|
makeDragHelpers = (
|
||||||
x: number,
|
x: number,
|
||||||
@ -176,7 +188,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),
|
||||||
@ -203,23 +218,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() {
|
||||||
@ -227,6 +226,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,
|
||||||
@ -235,3 +235,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,11 +1,13 @@
|
|||||||
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 { doAndWaitForImageDiff } from '../test-utils'
|
||||||
|
|
||||||
export class ToolbarFixture {
|
export class ToolbarFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
|
|
||||||
extrudeButton!: Locator
|
extrudeButton!: Locator
|
||||||
|
loftButton!: Locator
|
||||||
|
shellButton!: Locator
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
lineBtn!: Locator
|
lineBtn!: Locator
|
||||||
@ -26,6 +28,8 @@ export class ToolbarFixture {
|
|||||||
reConstruct = (page: Page) => {
|
reConstruct = (page: Page) => {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
|
this.loftButton = page.getByTestId('loft')
|
||||||
|
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')
|
||||||
|
@ -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()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,25 +1,51 @@
|
|||||||
import { test, expect, AuthenticatedApp } from './fixtures/fixtureSetup'
|
import { test, expect, Page } from './zoo-test'
|
||||||
import { EditorFixture } from './fixtures/editorFixture'
|
import { EditorFixture } from './fixtures/editorFixture'
|
||||||
import { SceneFixture } from './fixtures/sceneFixture'
|
import { SceneFixture } from './fixtures/sceneFixture'
|
||||||
import { ToolbarFixture } from './fixtures/toolbarFixture'
|
import { ToolbarFixture } from './fixtures/toolbarFixture'
|
||||||
|
import fs from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { getUtils } from './test-utils'
|
||||||
|
|
||||||
// test file is for testing point an click code gen functionality that's not sketch mode related
|
// test file is for testing point an click code gen functionality that's not sketch mode related
|
||||||
|
|
||||||
test(
|
test('verify extruding circle works', async ({
|
||||||
'verify extruding circle works',
|
context,
|
||||||
{ tag: ['@skipWin'] },
|
homePage,
|
||||||
async ({ app, cmdBar, editor, toolbar, scene }) => {
|
cmdBar,
|
||||||
test.skip(
|
editor,
|
||||||
process.platform === 'win32',
|
toolbar,
|
||||||
'Fails on windows in CI, can not be replicated locally on windows.'
|
scene,
|
||||||
)
|
}) => {
|
||||||
const file = await app.getInputFile('test-circle-extrude.kcl')
|
// TODO: fix this test on windows after the electron migration
|
||||||
await app.initialise(file)
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
const file = await fs.readFile(
|
||||||
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../',
|
||||||
|
'./src/wasm-lib/tests/executor/inputs/test-circle-extrude.kcl'
|
||||||
|
),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await context.addInitScript((file) => {
|
||||||
|
localStorage.setItem('persistCode', file)
|
||||||
|
}, file)
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => {
|
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
||||||
await scene.clickNoWhere()
|
|
||||||
await expect(toolbar.extrudeButton).toBeEnabled()
|
await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => {
|
||||||
|
await scene.clickNoWhere()
|
||||||
|
await expect(toolbar.extrudeButton).toBeEnabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('check code model connection works and that button is still enable once circle is selected ', async () => {
|
||||||
|
await moveToCircle()
|
||||||
|
const circleSnippet =
|
||||||
|
'circle({ center = [318.33, 168.1], radius = 182.8 }, %)'
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: ["constsketch002=startSketchOn('XZ')"],
|
||||||
|
highlightedCode: circleSnippet,
|
||||||
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('check code model connection works and that button is still enable once circle is selected ', async () => {
|
await test.step('check code model connection works and that button is still enable once circle is selected ', async () => {
|
||||||
@ -27,7 +53,7 @@ test(
|
|||||||
const circleSnippet =
|
const circleSnippet =
|
||||||
'circle({ center = [318.33, 168.1], radius = 182.8 }, %)'
|
'circle({ center = [318.33, 168.1], radius = 182.8 }, %)'
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: [],
|
activeLines: ["constsketch002=startSketchOn('XZ')"],
|
||||||
highlightedCode: circleSnippet,
|
highlightedCode: circleSnippet,
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
@ -40,39 +66,42 @@ test(
|
|||||||
})
|
})
|
||||||
await expect(toolbar.extrudeButton).toBeEnabled()
|
await expect(toolbar.extrudeButton).toBeEnabled()
|
||||||
})
|
})
|
||||||
|
await expect(toolbar.extrudeButton).toBeEnabled()
|
||||||
|
})
|
||||||
|
|
||||||
await test.step('do extrude flow and check extrude code is added to editor', async () => {
|
await test.step('do extrude flow and check extrude code is added to editor', async () => {
|
||||||
await toolbar.extrudeButton.click()
|
await toolbar.extrudeButton.click()
|
||||||
|
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'distance',
|
currentArgKey: 'distance',
|
||||||
currentArgValue: '5',
|
currentArgValue: '5',
|
||||||
headerArguments: { Selection: '1 face', Distance: '' },
|
headerArguments: { Selection: '1 face', Distance: '' },
|
||||||
highlightedHeaderArg: 'distance',
|
highlightedHeaderArg: 'distance',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
|
|
||||||
const expectString = 'extrude001 = extrude(5, sketch001)'
|
|
||||||
await editor.expectEditor.not.toContain(expectString)
|
|
||||||
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: { Selection: '1 face', Distance: '5' },
|
|
||||||
commandName: 'Extrude',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
|
|
||||||
await editor.expectEditor.toContain(expectString)
|
|
||||||
})
|
})
|
||||||
}
|
await cmdBar.progressCmdBar()
|
||||||
)
|
|
||||||
|
const expectString = 'extrude001 = extrude(5, sketch001)'
|
||||||
|
await editor.expectEditor.not.toContain(expectString)
|
||||||
|
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: { Selection: '1 face', Distance: '5' },
|
||||||
|
commandName: 'Extrude',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
|
await editor.expectEditor.toContain(expectString)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test.describe('verify sketch on chamfer works', () => {
|
test.describe('verify sketch on chamfer works', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const _sketchOnAChamfer =
|
const _sketchOnAChamfer =
|
||||||
(
|
(
|
||||||
app: AuthenticatedApp,
|
page: Page,
|
||||||
editor: EditorFixture,
|
editor: EditorFixture,
|
||||||
toolbar: ToolbarFixture,
|
toolbar: ToolbarFixture,
|
||||||
scene: SceneFixture
|
scene: SceneFixture
|
||||||
@ -124,7 +153,7 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
await toolbar.startSketchPlaneSelection()
|
await toolbar.startSketchPlaneSelection()
|
||||||
await clickChamfer()
|
await clickChamfer()
|
||||||
// timeout wait for engine animation is unavoidable
|
// timeout wait for engine animation is unavoidable
|
||||||
await app.page.waitForTimeout(600)
|
await page.waitForTimeout(1000)
|
||||||
await editor.expectEditor.toContain(afterChamferSelectSnippet)
|
await editor.expectEditor.toContain(afterChamferSelectSnippet)
|
||||||
})
|
})
|
||||||
await test.step('make sure a basic sketch can be added', async () => {
|
await test.step('make sure a basic sketch can be added', async () => {
|
||||||
@ -135,7 +164,9 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
pixelDiff: 50,
|
pixelDiff: 50,
|
||||||
})
|
})
|
||||||
await rectangle2ndClick()
|
await rectangle2ndClick()
|
||||||
await editor.expectEditor.toContain(afterRectangle2ndClickSnippet)
|
await editor.expectEditor.toContain(afterRectangle2ndClickSnippet, {
|
||||||
|
shouldNormalise: true,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clean up so that `_sketchOnAChamfer` util can be called again', async () => {
|
await test.step('Clean up so that `_sketchOnAChamfer` util can be called again', async () => {
|
||||||
@ -150,24 +181,35 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
test(
|
test('works on all edge selections and can break up multi edges in a chamfer array', async ({
|
||||||
'works on all edge selections and can break up multi edges in a chamfer array',
|
context,
|
||||||
{ tag: ['@skipWin'] },
|
page,
|
||||||
async ({ app, editor, toolbar, scene }) => {
|
homePage,
|
||||||
test.skip(
|
editor,
|
||||||
process.platform === 'win32',
|
toolbar,
|
||||||
'Fails on windows in CI, can not be replicated locally on windows.'
|
scene,
|
||||||
)
|
}) => {
|
||||||
const file = await app.getInputFile('e2e-can-sketch-on-chamfer.kcl')
|
const file = await fs.readFile(
|
||||||
await app.initialise(file)
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../',
|
||||||
|
'./src/wasm-lib/tests/executor/inputs/e2e-can-sketch-on-chamfer.kcl'
|
||||||
|
),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await context.addInitScript((file) => {
|
||||||
|
localStorage.setItem('persistCode', file)
|
||||||
|
}, file)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene)
|
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||||
|
|
||||||
await sketchOnAChamfer({
|
await sketchOnAChamfer({
|
||||||
clickCoords: { x: 570, y: 220 },
|
clickCoords: { x: 570, y: 220 },
|
||||||
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
||||||
cameraTarget: { x: -150, y: -4500, z: -80 },
|
cameraTarget: { x: -150, y: -4500, z: -80 },
|
||||||
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
|
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
|
||||||
chamfer({length = 30,tags = [
|
chamfer({length = 30,tags = [
|
||||||
seg01,
|
seg01,
|
||||||
getNextAdjacentEdge(yo),
|
getNextAdjacentEdge(yo),
|
||||||
@ -175,10 +217,9 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
getOppositeEdge(seg01)
|
getOppositeEdge(seg01)
|
||||||
]}, %)`,
|
]}, %)`,
|
||||||
|
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet: 'sketch002 = startSketchOn(extrude001, seg03)',
|
||||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA002) - 90,
|
segAng(rectangleSegmentA002) - 90,
|
||||||
105.26
|
105.26
|
||||||
@ -189,13 +230,13 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`,
|
|> close(%)`,
|
||||||
})
|
})
|
||||||
|
|
||||||
await sketchOnAChamfer({
|
await sketchOnAChamfer({
|
||||||
clickCoords: { x: 690, y: 250 },
|
clickCoords: { x: 690, y: 250 },
|
||||||
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
||||||
cameraTarget: { x: -150, y: -4500, z: -80 },
|
cameraTarget: { x: -150, y: -4500, z: -80 },
|
||||||
beforeChamferSnippet: `angledLine([
|
beforeChamferSnippet: `angledLine([
|
||||||
segAng(rectangleSegmentA001) - 90,
|
segAng(rectangleSegmentA001) - 90,
|
||||||
217.26
|
217.26
|
||||||
], %, $seg01)chamfer({
|
], %, $seg01)chamfer({
|
||||||
@ -207,10 +248,9 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
]
|
]
|
||||||
}, %)`,
|
}, %)`,
|
||||||
|
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet: 'sketch003 = startSketchOn(extrude001, seg04)',
|
||||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA003) - 90,
|
segAng(rectangleSegmentA003) - 90,
|
||||||
106.84
|
106.84
|
||||||
@ -221,22 +261,21 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
], %, $rectangleSegmentC002)
|
], %, $rectangleSegmentC002)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`,
|
|> close(%)`,
|
||||||
})
|
})
|
||||||
await sketchOnAChamfer({
|
await sketchOnAChamfer({
|
||||||
clickCoords: { x: 677, y: 87 },
|
clickCoords: { x: 677, y: 87 },
|
||||||
cameraPos: { x: -6200, y: 1500, z: 6200 },
|
cameraPos: { x: -6200, y: 1500, z: 6200 },
|
||||||
cameraTarget: { x: 8300, y: 1100, z: 4800 },
|
cameraTarget: { x: 8300, y: 1100, z: 4800 },
|
||||||
beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({
|
beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({
|
||||||
length = 30,
|
length = 30,
|
||||||
tags = [
|
tags = [
|
||||||
getNextAdjacentEdge(yo),
|
getNextAdjacentEdge(yo),
|
||||||
getNextAdjacentEdge(seg02)
|
getNextAdjacentEdge(seg02)
|
||||||
]
|
]
|
||||||
}, %)`,
|
}, %)`,
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet: 'sketch003 = startSketchOn(extrude001, seg04)',
|
||||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
afterRectangle1stClickSnippet: 'startProfileAt([75.8, 317.2], %)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA003) - 90,
|
segAng(rectangleSegmentA003) - 90,
|
||||||
106.84
|
106.84
|
||||||
@ -247,20 +286,19 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
], %, $rectangleSegmentC002)
|
], %, $rectangleSegmentC002)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`,
|
|> close(%)`,
|
||||||
})
|
})
|
||||||
/// last one
|
/// last one
|
||||||
await sketchOnAChamfer({
|
await sketchOnAChamfer({
|
||||||
clickCoords: { x: 620, y: 300 },
|
clickCoords: { x: 620, y: 300 },
|
||||||
cameraPos: { x: -1100, y: -7700, z: 1600 },
|
cameraPos: { x: -1100, y: -7700, z: 1600 },
|
||||||
cameraTarget: { x: 1450, y: 670, z: 4000 },
|
cameraTarget: { x: 1450, y: 670, z: 4000 },
|
||||||
beforeChamferSnippet: `chamfer({
|
beforeChamferSnippet: `chamfer({
|
||||||
length = 30,
|
length = 30,
|
||||||
tags = [getNextAdjacentEdge(yo)]
|
tags = [getNextAdjacentEdge(yo)]
|
||||||
}, %)`,
|
}, %)`,
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet: 'sketch005 = startSketchOn(extrude001, seg06)',
|
||||||
'sketch005 = startSketchOn(extrude001, seg06)',
|
afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)',
|
afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005)
|
|
||||||
|
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA005) - 90,
|
segAng(rectangleSegmentA005) - 90,
|
||||||
@ -272,11 +310,11 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
], %, $rectangleSegmentC004)
|
], %, $rectangleSegmentC004)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`,
|
|> close(%)`,
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('verify at the end of the test that final code is what is expected', async () => {
|
await test.step('verify at the end of the test that final code is what is expected', async () => {
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|
|
||||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||||
@ -305,7 +343,7 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
tags = [getNextAdjacentEdge(yo)]
|
tags = [getNextAdjacentEdge(yo)]
|
||||||
}, %, $seg06)
|
}, %, $seg06)
|
||||||
sketch005 = startSketchOn(extrude001, seg06)
|
sketch005 = startSketchOn(extrude001, seg06)
|
||||||
|> startProfileAt([-23.43, 19.69], %)
|
|> startProfileAt([-23.43,19.69], %)
|
||||||
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA005) - 90,
|
segAng(rectangleSegmentA005) - 90,
|
||||||
@ -318,7 +356,7 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
sketch004 = startSketchOn(extrude001, seg05)
|
sketch004 = startSketchOn(extrude001, seg05)
|
||||||
|> startProfileAt([82.57, 322.96], %)
|
|> startProfileAt([82.57,322.96], %)
|
||||||
|> angledLine([0, 11.16], %, $rectangleSegmentA004)
|
|> angledLine([0, 11.16], %, $rectangleSegmentA004)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA004) - 90,
|
segAng(rectangleSegmentA004) - 90,
|
||||||
@ -331,7 +369,7 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
sketch003 = startSketchOn(extrude001, seg04)
|
sketch003 = startSketchOn(extrude001, seg04)
|
||||||
|> startProfileAt([-209.64, 255.28], %)
|
|> startProfileAt([-209.64,255.28], %)
|
||||||
|> angledLine([0, 11.56], %, $rectangleSegmentA003)
|
|> angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA003) - 90,
|
segAng(rectangleSegmentA003) - 90,
|
||||||
@ -344,7 +382,7 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
sketch002 = startSketchOn(extrude001, seg03)
|
sketch002 = startSketchOn(extrude001, seg03)
|
||||||
|> startProfileAt([205.96, 254.59], %)
|
|> startProfileAt([205.96,254.59], %)
|
||||||
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA002) - 90,
|
segAng(rectangleSegmentA002) - 90,
|
||||||
@ -357,43 +395,50 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`,
|
`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
|
||||||
'Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array',
|
|
||||||
{ tag: ['@skipWin'] },
|
|
||||||
async ({ app, editor, toolbar, scene }) => {
|
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'Fails on windows in CI, can not be replicated locally on windows.'
|
|
||||||
)
|
)
|
||||||
const file = await app.getInputFile(
|
})
|
||||||
'e2e-can-sketch-on-chamfer-no-pipeExpr.kcl'
|
})
|
||||||
)
|
|
||||||
await app.initialise(file)
|
|
||||||
|
|
||||||
const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene)
|
test('Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array', async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
|
const file = await fs.readFile(
|
||||||
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../',
|
||||||
|
'./src/wasm-lib/tests/executor/inputs/e2e-can-sketch-on-chamfer-no-pipeExpr.kcl'
|
||||||
|
),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await context.addInitScript((file) => {
|
||||||
|
localStorage.setItem('persistCode', file)
|
||||||
|
}, file)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await sketchOnAChamfer({
|
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||||
clickCoords: { x: 570, y: 220 },
|
|
||||||
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
await sketchOnAChamfer({
|
||||||
cameraTarget: { x: -150, y: -4500, z: -80 },
|
clickCoords: { x: 570, y: 220 },
|
||||||
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
|
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
||||||
|
cameraTarget: { x: -150, y: -4500, z: -80 },
|
||||||
|
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
|
||||||
chamfer({length=30,tags=[
|
chamfer({length=30,tags=[
|
||||||
seg01,
|
seg01,
|
||||||
getNextAdjacentEdge(yo),
|
getNextAdjacentEdge(yo),
|
||||||
getNextAdjacentEdge(seg02),
|
getNextAdjacentEdge(seg02),
|
||||||
getOppositeEdge(seg01)
|
getOppositeEdge(seg01)
|
||||||
]}, extrude001)`,
|
]}, extrude001)`,
|
||||||
beforeChamferSnippetEnd: '}, extrude001)',
|
beforeChamferSnippetEnd: '}, extrude001)',
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet: 'sketch002 = startSketchOn(extrude001, seg03)',
|
||||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA002) - 90,
|
segAng(rectangleSegmentA002) - 90,
|
||||||
105.26
|
105.26
|
||||||
@ -404,9 +449,9 @@ test.describe('verify sketch on chamfer works', () => {
|
|||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`,
|
|> close(%)`,
|
||||||
})
|
})
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([75.8, 317.2], %)
|
|> startProfileAt([75.8, 317.2], %)
|
||||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
@ -446,50 +491,56 @@ sketch002 = startSketchOn(extrude001, seg03)
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`,
|
`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`Verify axis, origin, and horizontal snapping`, async ({
|
test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||||
app,
|
page,
|
||||||
|
homePage,
|
||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
|
const viewPortSize = { width: 1200, height: 500 }
|
||||||
|
|
||||||
|
await page.setBodyDimensions(viewPortSize)
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Constants and locators
|
// Constants and locators
|
||||||
// These are mappings from screenspace to KCL coordinates,
|
// These are mappings from screenspace to KCL coordinates,
|
||||||
// until we merge in our coordinate system helpers
|
// until we merge in our coordinate system helpers
|
||||||
const xzPlane = [
|
const xzPlane = [
|
||||||
app.viewPortSize.width * 0.65,
|
viewPortSize.width * 0.65,
|
||||||
app.viewPortSize.height * 0.3,
|
viewPortSize.height * 0.3,
|
||||||
] as const
|
] as const
|
||||||
const originSloppy = {
|
const originSloppy = {
|
||||||
screen: [
|
screen: [
|
||||||
app.viewPortSize.width / 2 + 3, // 3px off the center of the screen
|
viewPortSize.width / 2 + 3, // 3px off the center of the screen
|
||||||
app.viewPortSize.height / 2,
|
viewPortSize.height / 2,
|
||||||
],
|
],
|
||||||
kcl: [0, 0],
|
kcl: [0, 0],
|
||||||
} as const
|
} as const
|
||||||
const xAxisSloppy = {
|
const xAxisSloppy = {
|
||||||
screen: [
|
screen: [
|
||||||
app.viewPortSize.width * 0.75,
|
viewPortSize.width * 0.75,
|
||||||
app.viewPortSize.height / 2 - 3, // 3px off the X-axis
|
viewPortSize.height / 2 - 3, // 3px off the X-axis
|
||||||
],
|
],
|
||||||
kcl: [16.95, 0],
|
kcl: [20.34, 0],
|
||||||
} as const
|
} as const
|
||||||
const offYAxis = {
|
const offYAxis = {
|
||||||
screen: [
|
screen: [
|
||||||
app.viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range
|
viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range
|
||||||
app.viewPortSize.height * 0.3,
|
viewPortSize.height * 0.3,
|
||||||
],
|
],
|
||||||
kcl: [6.78, 6.78],
|
kcl: [8.14, 6.78],
|
||||||
} as const
|
} as const
|
||||||
const yAxisSloppy = {
|
const yAxisSloppy = {
|
||||||
screen: [
|
screen: [
|
||||||
app.viewPortSize.width / 2 + 5, // 5px off the Y-axis
|
viewPortSize.width / 2 + 5, // 5px off the Y-axis
|
||||||
app.viewPortSize.height * 0.3,
|
viewPortSize.height * 0.3,
|
||||||
],
|
],
|
||||||
kcl: [0, 6.78],
|
kcl: [0, 6.78],
|
||||||
} as const
|
} as const
|
||||||
@ -510,15 +561,13 @@ test(`Verify axis, origin, and horizontal snapping`, async ({
|
|||||||
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
|
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
|
||||||
}
|
}
|
||||||
|
|
||||||
await app.initialise()
|
|
||||||
|
|
||||||
await test.step(`Start a sketch on the XZ plane`, async () => {
|
await test.step(`Start a sketch on the XZ plane`, async () => {
|
||||||
await editor.closePane()
|
await editor.closePane()
|
||||||
await toolbar.startSketchPlaneSelection()
|
await toolbar.startSketchPlaneSelection()
|
||||||
await moveToXzPlane()
|
await moveToXzPlane()
|
||||||
await clickOnXzPlane()
|
await clickOnXzPlane()
|
||||||
// timeout wait for engine animation is unavoidable
|
// timeout wait for engine animation is unavoidable
|
||||||
await app.page.waitForTimeout(600)
|
await page.waitForTimeout(600)
|
||||||
await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane)
|
await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane)
|
||||||
})
|
})
|
||||||
await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => {
|
await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => {
|
||||||
@ -552,20 +601,127 @@ test(`Verify axis, origin, and horizontal snapping`, async ({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Verify user can double-click to edit a sketch`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
const initialCode = `closedSketch = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [8, 5], radius = 2 }, %)
|
||||||
|
openSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-5, 0], %)
|
||||||
|
|> lineTo([0, 5], %)
|
||||||
|
|> xLine(5, %)
|
||||||
|
|> tangentialArcTo([10, 0], %)
|
||||||
|
`
|
||||||
|
const viewPortSize = { width: 1000, height: 500 }
|
||||||
|
await page.setBodyDimensions(viewPortSize)
|
||||||
|
|
||||||
|
await context.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, initialCode)
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
const pointInsideCircle = {
|
||||||
|
x: viewPortSize.width * 0.63,
|
||||||
|
y: viewPortSize.height * 0.5,
|
||||||
|
}
|
||||||
|
const pointOnPathAfterSketching = {
|
||||||
|
x: viewPortSize.width * 0.65,
|
||||||
|
y: viewPortSize.height * 0.5,
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] =
|
||||||
|
scene.makeMouseHelpers(
|
||||||
|
pointOnPathAfterSketching.x,
|
||||||
|
pointOnPathAfterSketching.y
|
||||||
|
)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const [_clickCircle, moveToCircle, dblClickCircle] = scene.makeMouseHelpers(
|
||||||
|
pointInsideCircle.x,
|
||||||
|
pointInsideCircle.y
|
||||||
|
)
|
||||||
|
|
||||||
|
const exitSketch = async () => {
|
||||||
|
await test.step(`Exit sketch mode`, async () => {
|
||||||
|
await toolbar.exitSketchBtn.click()
|
||||||
|
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||||
|
await expect(toolbar.startSketchBtn).toBeEnabled()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step(`Double-click on the closed sketch`, async () => {
|
||||||
|
await moveToCircle()
|
||||||
|
await dblClickCircle()
|
||||||
|
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
||||||
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: [`|>circle({center=[8,5],radius=2},%)`],
|
||||||
|
highlightedCode: 'circle({center=[8,5],radius=2},%)',
|
||||||
|
diagnostics: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
await exitSketch()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
// Drag the sketch line out of the axis view which blocks the click
|
||||||
|
await page.dragAndDrop('#stream', '#stream', {
|
||||||
|
sourcePosition: {
|
||||||
|
x: viewPortSize.width * 0.7,
|
||||||
|
y: viewPortSize.height * 0.5,
|
||||||
|
},
|
||||||
|
targetPosition: {
|
||||||
|
x: viewPortSize.width * 0.7,
|
||||||
|
y: viewPortSize.height * 0.4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
await test.step(`Double-click on the open sketch`, async () => {
|
||||||
|
await moveToOpenPath()
|
||||||
|
await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15)
|
||||||
|
// There is a full execution after exiting sketch that clears the scene.
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await dblClickOpenPath()
|
||||||
|
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
||||||
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
|
// Wait for enter sketch mode to complete
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: [`|>tangentialArcTo([10,0],%)`],
|
||||||
|
highlightedCode: 'tangentialArcTo([10,0],%)',
|
||||||
|
diagnostics: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test(`Offset plane point-and-click`, async ({
|
test(`Offset plane point-and-click`, async ({
|
||||||
app,
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
await app.initialise()
|
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 700, y: 150 }
|
const testPoint = { x: 700, y: 150 }
|
||||||
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
const expectedOutput = `plane001 = offsetPlane('XZ', 5)`
|
const expectedOutput = `plane001 = offsetPlane('XZ', 5)`
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await test.step(`Look for the blue of the XZ plane`, async () => {
|
await test.step(`Look for the blue of the XZ plane`, async () => {
|
||||||
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
||||||
})
|
})
|
||||||
@ -601,3 +757,277 @@ test(`Offset plane point-and-click`, async ({
|
|||||||
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const loftPointAndClickCases = [
|
||||||
|
{ shouldPreselect: true },
|
||||||
|
{ shouldPreselect: false },
|
||||||
|
]
|
||||||
|
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||||
|
test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||||
|
plane001 = offsetPlane('XZ', 50)
|
||||||
|
sketch002 = startSketchOn(plane001)
|
||||||
|
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||||
|
`
|
||||||
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// One dumb hardcoded screen pixel value
|
||||||
|
const testPoint = { x: 575, y: 200 }
|
||||||
|
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
const [clickOnSketch2] = scene.makeMouseHelpers(
|
||||||
|
testPoint.x,
|
||||||
|
testPoint.y + 80
|
||||||
|
)
|
||||||
|
const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
|
||||||
|
|
||||||
|
await test.step(`Look for the white of the sketch001 shape`, async () => {
|
||||||
|
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
async function selectSketches() {
|
||||||
|
await clickOnSketch1()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await clickOnSketch2()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldPreselect) {
|
||||||
|
await test.step(`Go through the command bar flow without preselected sketches`, async () => {
|
||||||
|
await toolbar.loftButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: { Selection: '' },
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
commandName: 'Loft',
|
||||||
|
})
|
||||||
|
await selectSketches()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: { Selection: '2 faces' },
|
||||||
|
commandName: 'Loft',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await test.step(`Preselect the two sketches`, async () => {
|
||||||
|
await selectSketches()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
|
||||||
|
await toolbar.loftButton.click()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: { Selection: '2 faces' },
|
||||||
|
commandName: 'Loft',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
|
await editor.expectEditor.toContain(loftDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [loftDeclaration],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const shellPointAndClickCapCases = [
|
||||||
|
{ shouldPreselect: true },
|
||||||
|
{ shouldPreselect: false },
|
||||||
|
]
|
||||||
|
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
||||||
|
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||||
|
extrude001 = extrude(30, sketch001)
|
||||||
|
`
|
||||||
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// One dumb hardcoded screen pixel value
|
||||||
|
const testPoint = { x: 575, y: 200 }
|
||||||
|
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
const shellDeclaration =
|
||||||
|
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
|
||||||
|
|
||||||
|
await test.step(`Look for the grey of the shape`, async () => {
|
||||||
|
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!shouldPreselect) {
|
||||||
|
await test.step(`Go through the command bar flow without preselected faces`, async () => {
|
||||||
|
await toolbar.shellButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Thickness: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await clickOnCap()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 cap',
|
||||||
|
Thickness: '5',
|
||||||
|
},
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await test.step(`Preselect the cap`, async () => {
|
||||||
|
await clickOnCap()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
||||||
|
await toolbar.shellButton.click()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 cap',
|
||||||
|
Thickness: '5',
|
||||||
|
},
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
|
await editor.expectEditor.toContain(shellDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [shellDeclaration],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
await scene.expectPixelColor([146, 146, 146], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Shell point-and-click wall', async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-20, 20], %)
|
||||||
|
|> xLine(40, %)
|
||||||
|
|> yLine(-60, %)
|
||||||
|
|> xLine(-40, %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(40, sketch001)
|
||||||
|
`
|
||||||
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// One dumb hardcoded screen pixel value
|
||||||
|
const testPoint = { x: 580, y: 180 }
|
||||||
|
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
|
||||||
|
const mutatedCode = 'xLine(-40, %, $seg01)'
|
||||||
|
const shellDeclaration =
|
||||||
|
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
|
||||||
|
const formattedOutLastLine = '}, extrude001)'
|
||||||
|
|
||||||
|
await test.step(`Look for the grey of the shape`, async () => {
|
||||||
|
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
|
||||||
|
await toolbar.shellButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Thickness: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await clickOnCap()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await clickOnWall()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'review',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 cap, 1 face',
|
||||||
|
Thickness: '5',
|
||||||
|
},
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
|
await editor.expectEditor.toContain(mutatedCode)
|
||||||
|
await editor.expectEditor.toContain(shellDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [formattedOutLastLine],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -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,14 +553,15 @@ 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
|
||||||
@ -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()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ try {
|
|||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((line) => line && line.length > 1)
|
.filter((line) => line && line.length > 1)
|
||||||
.forEach((line) => {
|
.forEach((line) => {
|
||||||
|
// Allow line comments.
|
||||||
|
if (line.trimStart().startsWith('#')) return
|
||||||
const [key, value] = line.split('=')
|
const [key, value] = line.split('=')
|
||||||
// prefer env vars over secrets file
|
// prefer env vars over secrets file
|
||||||
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
|
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
|
||||||
|
@ -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 }) => {
|
||||||
@ -950,7 +954,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 +1168,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 After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |