Compare commits
104 Commits
achalmers/
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
cf4655ff6a | |||
e338c39eab | |||
d1d98897dc | |||
98b4aaea84 | |||
30d365aeb3 | |||
1faad75462 | |||
7af62399ac | |||
496a6c2768 | |||
5bb1bf520e | |||
a5377f50ff | |||
2fffa7e6d8 | |||
98c50eacc5 | |||
8f5c8447ea | |||
ded9f9b89f | |||
086bf9b736 | |||
441d957228 | |||
9e57034873 | |||
eb96d6539c | |||
513c76ecc8 | |||
51d9449280 | |||
129e07f059 | |||
137e528944 | |||
b9a07c9393 | |||
f18f975e1c | |||
6cd1af23e6 | |||
cc247ace0e | |||
68bcee9298 | |||
6366bc4766 | |||
7a21918223 | |||
8072f1db63 | |||
18e1855fa9 | |||
7be53c7d4a | |||
2bf20988ef | |||
1495cc6d18 | |||
f876e6ca3c | |||
60a0c811ab | |||
cab0c1e6a1 | |||
417d720b22 | |||
77293952c0 | |||
ea3d604b73 | |||
023a659491 | |||
dd3a2b14f9 | |||
c2500c39e6 | |||
38016e0137 | |||
424b409cc1 | |||
82a58e69c2 | |||
776b420031 | |||
1087d4223b | |||
089d6df889 | |||
efb067af58 | |||
2aa27eab01 | |||
9c47ac5b57 | |||
b263ef049a | |||
5ae1aecd74 | |||
68ae7e98f9 | |||
56771d561a | |||
f09411817c | |||
bed7ae3b8b | |||
c43510732c | |||
51f0b669a4 | |||
3cbedcd3e7 | |||
5d2fa43150 | |||
ec49b0752e | |||
3b171fb881 | |||
1bde0e7333 | |||
2301cc153a | |||
545332ee27 | |||
725e207bd1 | |||
75819f880e | |||
3c1c1303e1 | |||
60e62d435e | |||
78616f4074 | |||
0548409da0 | |||
566183ffa7 | |||
3e435bebf4 | |||
faae28b025 | |||
2d91549a02 | |||
dd052b35fd | |||
46be4e7eef | |||
46874f4d0a | |||
a132b35f99 | |||
6e04760e1c | |||
082b1cad74 | |||
412d1b7a99 | |||
cfdd22af74 | |||
68a11e7aa5 | |||
3139e18dc7 | |||
84ddae384e | |||
7566954b13 | |||
4b8c37c2c8 | |||
6d69b560be | |||
aa81d616cc | |||
7ee5a1c26b | |||
0763d1d11d | |||
0f52d7b21f | |||
25d252787c | |||
1bd5dc20ef | |||
cd6b9c2166 | |||
f79a59d415 | |||
eda97a3058 | |||
5cbb9d0697 | |||
be3f9b7b64 | |||
6179c66ae9 | |||
117957d7c2 |
@ -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-latest* ]]; 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-latest* ]]; then
|
||||||
yarn test:playwright:electron:windows || true
|
yarn test:playwright:electron:windows -- --shard=$1/$2 || true
|
||||||
elif [[ "$1" == macos-14* ]]; then
|
elif [[ "$3" == macos-14* ]]; 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-latest* ]]; 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-latest* ]]; 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-14* ]]; 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."
|
||||||
|
28
.github/workflows/build-apps.yml
vendored
@ -362,6 +362,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: prefered 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 +394,13 @@ 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: Tag nightly commit
|
||||||
uses: softprops/action-gh-release@v2
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
if: ${{ env.IS_RELEASE == '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 })
|
||||||
|
158
.github/workflows/e2e-tests.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: E2E Tests
|
name: E2E Tests
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main, pierremtb/move-tests-to-electron ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
|
||||||
@ -33,13 +33,13 @@ jobs:
|
|||||||
rust:
|
rust:
|
||||||
- 'src/wasm-lib/**'
|
- 'src/wasm-lib/**'
|
||||||
|
|
||||||
browser:
|
electron:
|
||||||
timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }}
|
timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }}
|
||||||
name: playwright:browser:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores]
|
os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large]
|
||||||
shardIndex: [1, 2, 3, 4]
|
shardIndex: [1, 2, 3, 4]
|
||||||
shardTotal: [4]
|
shardTotal: [4]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@ -68,7 +68,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}}
|
||||||
@ -123,13 +123,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 +186,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 +199,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 +216,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
|
|
||||||
|
6
.github/workflows/publish-apps-release.yml
vendored
@ -132,6 +132,12 @@ jobs:
|
|||||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async
|
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async
|
||||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
|
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
|
||||||
|
|
||||||
|
- name: Upload release files to Github
|
||||||
|
if: ${{ github.event_name == 'release' }}
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: 'out/Zoo*'
|
||||||
|
|
||||||
|
|
||||||
announce_release:
|
announce_release:
|
||||||
needs: [publish-apps-release]
|
needs: [publish-apps-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/
|
||||||
|
|
||||||
|
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 |
@ -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], %)
|
||||||
|
@ -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], (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
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -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,14 +30,14 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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, (i, result_so_far) {
|
||||||
return i + result_so_far
|
return i + result_so_far
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ 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()
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ fn decagon = (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, (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
|
||||||
|
@ -36,7 +36,7 @@ cube = startSketchAt([0, 0])
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|
|
||||||
fn cylinder = (radius, tag) => {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchAt([0, 0])
|
return startSketchAt([0, 0])
|
||||||
|> circle({
|
|> circle({
|
||||||
radius = radius,
|
radius = radius,
|
||||||
|
@ -36,7 +36,7 @@ cube = startSketchAt([0, 0])
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|
|
||||||
fn cylinder = (radius, tag) => {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchAt([0, 0])
|
return startSketchAt([0, 0])
|
||||||
|> circle({
|
|> circle({
|
||||||
radius = radius,
|
radius = radius,
|
||||||
|
21018
docs/kcl/std.json
@ -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,20 +146,20 @@ 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])
|
||||||
@ -167,7 +167,10 @@ 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
|
||||||
|
@ -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`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 |
|
||||||
|
|
||||||
|
@ -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 |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 |
|
|
||||||
|
|
||||||
|
|
@ -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()
|
||||||
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()
|
||||||
@ -64,9 +51,9 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
|
|
||||||
test('Opening and closing the code pane will consistently show error diagnostics', async ({
|
test('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,18 +120,18 @@ 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('When error is not in view you can click the badge to scroll to it', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
|
context,
|
||||||
}) => {
|
}) => {
|
||||||
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)
|
||||||
|
|
||||||
@ -164,24 +155,24 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
await expect(
|
await expect(
|
||||||
page
|
page
|
||||||
.getByText(
|
.getByText(
|
||||||
'sketch profile must lie entirely on one side of the revolution axis'
|
'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
|
||||||
)
|
)
|
||||||
.first()
|
.first()
|
||||||
).toBeVisible()
|
).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 +232,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 +297,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 +330,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,24 @@ test.describe('Command bar tests', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Fillet from command bar', async ({ page }) => {
|
test('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 +86,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 +146,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 +167,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 +214,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 +286,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,61 +47,63 @@ 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('if you click the format button it formats your code', 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(`sketch001 = startSketchOn('XY')
|
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|
||||||
|> line([20, 0], %)
|
|
||||||
|> line([0, 20], %)
|
|
||||||
|> line([-20, 0], %)
|
|
||||||
|> close(%)`)
|
|
||||||
await page.locator('#code-pane button:first-child').click()
|
|
||||||
await page.locator('button:has-text("Format code")').click()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, -10], %)
|
|> 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 +127,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 +143,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 +214,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 +261,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 +295,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 +348,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 +364,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 +407,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,20 +448,23 @@ 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: 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()
|
||||||
|
|
||||||
/* 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
|
$ error
|
||||||
const topAng = 30
|
const topAng = 30
|
||||||
const bottomAng = 25
|
const bottomAng = 25
|
||||||
*/
|
*/
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type('$ error')
|
await page.keyboard.type('$ error')
|
||||||
|
|
||||||
@ -518,31 +522,36 @@ 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('error with 2 source ranges gets 2 diagnostics', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`length = .750
|
`length = .750
|
||||||
width = 0.500
|
width = 0.500
|
||||||
height = 0.500
|
height = 0.500
|
||||||
dia = 4
|
dia = 4
|
||||||
|
|
||||||
fn squareHole = (l, w) => {
|
fn squareHole = (l, w) => {
|
||||||
squareHoleSketch = startSketchOn('XY')
|
squareHoleSketch = startSketchOn('XY')
|
||||||
|> startProfileAt([-width / 2, -length / 2], %)
|
|> startProfileAt([-width / 2, -length / 2], %)
|
||||||
|> lineTo([width / 2, -length / 2], %)
|
|> lineTo([width / 2, -length / 2], %)
|
||||||
|> lineTo([width / 2, length / 2], %)
|
|> lineTo([width / 2, length / 2], %)
|
||||||
|> lineTo([-width / 2, length / 2], %)
|
|> lineTo([-width / 2, length / 2], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
return squareHoleSketch
|
return squareHoleSketch
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
@ -568,9 +577,9 @@ test.describe('Editor tests', () => {
|
|||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = dia/2 }, %)
|
|> circle({ center: [0, 0], radius: dia/2 }, %)
|
||||||
|> hole(squareHole(length, width, height), %)
|
|> hole(squareHole(length, width, height), %)
|
||||||
|> extrude(height, %)`)
|
|> extrude(height, %)`)
|
||||||
|
|
||||||
// error in gutter
|
// error in gutter
|
||||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
@ -583,38 +592,38 @@ test.describe('Editor tests', () => {
|
|||||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
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 +634,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 +704,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 +778,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 +854,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 +906,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 +920,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 +960,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 +974,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 +987,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 +1002,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 +1082,6 @@ test.describe('Editor tests', () => {
|
|||||||
})
|
})
|
||||||
.toBeGreaterThan(15)
|
.toBeGreaterThan(15)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -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,20 @@ export class EditorFixture {
|
|||||||
openPane() {
|
openPane() {
|
||||||
return openPane(this.page, this.paneButtonTestId)
|
return openPane(this.page, this.paneButtonTestId)
|
||||||
}
|
}
|
||||||
|
scrollToText(text: string) {
|
||||||
|
return this.page.evaluate((scrollToText: string) => {
|
||||||
|
// editorManager is available on the window object.
|
||||||
|
// @ts-ignore
|
||||||
|
let index = editorManager._editorView.docView.view.state.doc
|
||||||
|
.toString()
|
||||||
|
.indexOf(scrollToText)
|
||||||
|
// @ts-ignore
|
||||||
|
editorManager._editorView.dispatch({
|
||||||
|
selection: {
|
||||||
|
anchor: index,
|
||||||
|
},
|
||||||
|
scrollIntoView: true,
|
||||||
|
})
|
||||||
|
}, text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,11 @@ 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 }
|
||||||
|
|
||||||
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 +49,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 +59,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
|
||||||
|
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 +79,24 @@ 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)
|
||||||
|
|
||||||
|
await page.setViewportSize(this.viewPortSize)
|
||||||
|
|
||||||
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),
|
||||||
@ -227,6 +242,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,
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
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
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
lineBtn!: Locator
|
lineBtn!: Locator
|
||||||
@ -26,6 +27,7 @@ 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.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,63 @@
|
|||||||
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'
|
||||||
|
|
||||||
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 }) => {
|
||||||
|
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()
|
// *and* that the code is shown in the editor
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
// Test that the onboarding pane loaded
|
'// Shelf Bracket'
|
||||||
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
)
|
||||||
|
}
|
||||||
// *and* that the code is shown in the editor
|
)
|
||||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
|
||||||
})
|
|
||||||
|
|
||||||
test(
|
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 })
|
||||||
@ -93,320 +77,361 @@ test.describe('Onboarding tests', () => {
|
|||||||
'// Shelf Bracket'
|
'// Shelf Bracket'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
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 isnt 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(
|
||||||
'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 +443,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 +463,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,8 +479,8 @@ 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 () => {
|
||||||
@ -480,11 +504,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,49 @@
|
|||||||
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')
|
const file = await fs.readFile(
|
||||||
await app.initialise(file)
|
path.resolve(
|
||||||
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
__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 +51,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 +64,40 @@ 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', () => {
|
||||||
const _sketchOnAChamfer =
|
const _sketchOnAChamfer =
|
||||||
(
|
(
|
||||||
app: AuthenticatedApp,
|
page: Page,
|
||||||
editor: EditorFixture,
|
editor: EditorFixture,
|
||||||
toolbar: ToolbarFixture,
|
toolbar: ToolbarFixture,
|
||||||
scene: SceneFixture
|
scene: SceneFixture
|
||||||
@ -124,7 +149,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 +160,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 +177,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 +213,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 +226,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 +244,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 +257,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 +282,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 +306,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 +339,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 +352,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 +365,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 +378,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 +391,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 +445,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 +487,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 +557,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 +597,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 +753,104 @@ 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,
|
||||||
|
homePage,
|
||||||
|
page,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
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 page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await context.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, initialCode)
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -1,46 +1,37 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page } from './zoo-test'
|
||||||
import { join } from 'path'
|
import path from 'path'
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
import {
|
import { getUtils, executorInputPath } from './test-utils'
|
||||||
getUtils,
|
|
||||||
setup,
|
|
||||||
setupElectron,
|
|
||||||
tearDown,
|
|
||||||
executorInputPath,
|
|
||||||
} from './test-utils'
|
|
||||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Regression tests', () => {
|
test.describe('Regression tests', () => {
|
||||||
// bugs we found that don't fit neatly into other categories
|
// bugs we found that don't fit neatly into other categories
|
||||||
test('bad model has inline error #3251', async ({ page }) => {
|
test('bad model has inline error #3251', async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
}) => {
|
||||||
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
||||||
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
||||||
// Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
|
// Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch2 = startSketchOn("XY")
|
`sketch2 = startSketchOn("XY")
|
||||||
sketch001 = startSketchAt([-0, -0])
|
sketch001 = startSketchAt([-0, -0])
|
||||||
|> line([0, 0], %)
|
|> line([0, 0], %)
|
||||||
|> line([-4.84, -5.29], %)
|
|> line([-4.84, -5.29], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`
|
|> close(%)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// error in guter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
@ -56,6 +47,7 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
})
|
})
|
||||||
test('user should not have to press down twice in cmdbar', async ({
|
test('user should not have to press down twice in cmdbar', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
||||||
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
||||||
@ -64,26 +56,38 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch2 = startSketchOn("XY")
|
`sketch001 = startSketchOn('XY')
|
||||||
sketch001 = startSketchAt([-0, -0])
|
|> startProfileAt([82.33, 238.21], %)
|
||||||
|> line([0, 0], %)
|
|> angledLine([0, 288.63], %, $rectangleSegmentA001)
|
||||||
|> line([-4.84, -5.29], %)
|
|> angledLine([
|
||||||
|
segAng(rectangleSegmentA001) - 90,
|
||||||
|
197.97
|
||||||
|
], %, $rectangleSegmentB001)
|
||||||
|
|> angledLine([
|
||||||
|
segAng(rectangleSegmentA001),
|
||||||
|
-segLen(rectangleSegmentA001)
|
||||||
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`
|
|> close(%)
|
||||||
|
extrude001 = extrude(50, sketch001)
|
||||||
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await page.goto('/')
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await test.step('Check arrow down works', async () => {
|
await test.step('Check arrow down works', async () => {
|
||||||
|
await page.getByTestId('command-bar-open-button').hover()
|
||||||
await page.getByTestId('command-bar-open-button').click()
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
await page
|
const floppy = page.getByRole('option', {
|
||||||
.getByRole('option', { name: 'floppy disk arrow Export' })
|
name: 'floppy disk arrow Export',
|
||||||
.click()
|
})
|
||||||
|
|
||||||
|
await floppy.click()
|
||||||
|
|
||||||
// press arrow down key twice
|
// press arrow down key twice
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
@ -115,21 +119,22 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
test('executes on load', async ({ page }) => {
|
test('executes on load', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('-XZ')
|
`sketch001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([-6.95, 4.98], %)
|
|> startProfileAt([-6.95, 4.98], %)
|
||||||
|> line([25.1, 0.41], %)
|
|> line([25.1, 0.41], %)
|
||||||
|> line([0.73, -14.93], %)
|
|> line([0.73, -14.93], %)
|
||||||
|> line([-23.44, 0.52], %)`
|
|> line([-23.44, 0.52], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// expand variables section
|
// expand variables section
|
||||||
const variablesTabButton = page.getByTestId('variables-pane-button')
|
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||||
@ -148,14 +153,15 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('re-executes', async ({ page }) => {
|
test('re-executes', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem('persistCode', `myVar = 5`)
|
localStorage.setItem('persistCode', `myVar = 5`)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const variablesTabButton = page.getByTestId('variables-pane-button')
|
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||||
await variablesTabButton.click()
|
await variablesTabButton.click()
|
||||||
@ -174,32 +180,33 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
page.locator('.pretty-json-container >> text=myVar:67')
|
page.locator('.pretty-json-container >> text=myVar:67')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
test('ProgramMemory can be serialised', async ({ page }) => {
|
test('ProgramMemory can be serialised', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`part = startSketchOn('XY')
|
`part = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([0, 1], %)
|
|> line([0, 1], %)
|
||||||
|> line([1, 0], %)
|
|> line([1, 0], %)
|
||||||
|> line([0, -1], %)
|
|> line([0, -1], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(1, %)
|
|> extrude(1, %)
|
||||||
|> patternLinear3d({
|
|> patternLinear3d({
|
||||||
axis: [1, 0, 1],
|
axis: [1, 0, 1],
|
||||||
repetitions: 3,
|
repetitions: 3,
|
||||||
distance: 6
|
distance: 6
|
||||||
}, %)`
|
}, %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
const messages: string[] = []
|
const messages: string[] = []
|
||||||
|
|
||||||
// Listen for all console events and push the message text to an array
|
// Listen for all console events and push the message text to an array
|
||||||
page.on('console', (message) => messages.push(message.text()))
|
page.on('console', (message) => messages.push(message.text()))
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -212,19 +219,26 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
test('ensure the Zoo logo is not a link in browser app', async ({ page }) => {
|
|
||||||
|
// Not relevant to us anymore, or at least for the time being.
|
||||||
|
test.skip('ensure the Zoo logo is not a link in browser app', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const zooLogo = page.locator('[data-testid="app-logo"]')
|
const zooLogo = page.locator('[data-testid="app-logo"]')
|
||||||
// Make sure it's not a link
|
// Make sure it's not a link
|
||||||
await expect(zooLogo).not.toHaveAttribute('href')
|
await expect(zooLogo).not.toHaveAttribute('href')
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Position _ Is Out Of Range... regression test',
|
'Position _ Is Out Of Range... regression test',
|
||||||
{ tag: ['@skipWin'] },
|
{ tag: ['@skipWin'] },
|
||||||
async ({ page }) => {
|
async ({ context, page, homePage }) => {
|
||||||
// SKip on windows, its being weird.
|
// SKip on windows, its being weird.
|
||||||
test.skip(
|
test.skip(
|
||||||
process.platform === 'win32',
|
process.platform === 'win32',
|
||||||
@ -233,25 +247,26 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await page.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`exampleSketch = startSketchOn("XZ")
|
`exampleSketch = startSketchOn("XZ")
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine({ angle: 50, length: 45 }, %)
|
|> angledLine({ angle: 50, length: 45 }, %)
|
||||||
|> yLineTo(0, %)
|
|> yLineTo(0, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|>
|
|>
|
||||||
|
|
||||||
example = extrude(5, exampleSketch)
|
example = extrude(5, exampleSketch)
|
||||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await page.goto('/')
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// error in guter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||||
timeout: 1_000,
|
timeout: 1_000,
|
||||||
@ -293,12 +308,12 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toContainText(`exampleSketch = startSketchOn("XZ")
|
.toContainText(`exampleSketch = startSketchOn("XZ")
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine({ angle: 50, length: 45 }, %)
|
|> angledLine({ angle: 50, length: 45 }, %)
|
||||||
|> yLineTo(0, %)
|
|> yLineTo(0, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|
|
||||||
thing: "blah"`)
|
thing: "blah"`)
|
||||||
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
}
|
}
|
||||||
@ -306,6 +321,7 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
|
|
||||||
test('when engine fails export we handle the failure and alert the user', async ({
|
test('when engine fails export we handle the failure and alert the user', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
@ -316,9 +332,10 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
|
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -374,7 +391,6 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.clearCommandLogs()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
@ -408,7 +424,7 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
test(
|
test(
|
||||||
'ensure you can not export while an export is already going',
|
'ensure you can not export while an export is already going',
|
||||||
{ tag: ['@skipLinux', '@skipWin'] },
|
{ tag: ['@skipLinux', '@skipWin'] },
|
||||||
async ({ page }) => {
|
async ({ page, homePage }) => {
|
||||||
// This is being weird on ubuntu and windows.
|
// This is being weird on ubuntu and windows.
|
||||||
test.skip(
|
test.skip(
|
||||||
// eslint-disable-next-line jest/valid-title
|
// eslint-disable-next-line jest/valid-title
|
||||||
@ -428,9 +444,10 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -500,20 +517,17 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
test(
|
test(
|
||||||
`Network health indicator only appears in modeling view`,
|
`Network health indicator only appears in modeling view`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName: _ }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await context.folderSetupFn(async (dir) => {
|
||||||
testInfo,
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
folderSetupFn: async (dir) => {
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
const bracketDir = join(dir, 'bracket')
|
await fsp.copyFile(
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
await fsp.copyFile(
|
path.join(bracketDir, 'main.kcl')
|
||||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
)
|
||||||
join(bracketDir, 'main.kcl')
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Locators
|
// Locators
|
||||||
@ -539,18 +553,17 @@ 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,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Constants and locators
|
// Constants and locators
|
||||||
const planeColor: [number, number, number] = [170, 220, 170]
|
const planeColor: [number, number, number] = [161, 220, 155]
|
||||||
const bgColor: [number, number, number] = [27, 27, 27]
|
const bgColor: [number, number, number] = [27, 27, 27]
|
||||||
const middlePixelIsColor = async (color: [number, number, number]) => {
|
const middlePixelIsColor = async (color: [number, number, number]) => {
|
||||||
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
||||||
@ -561,8 +574,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()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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,12 @@ 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 }) => {
|
||||||
|
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 |
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 |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 38 KiB |
@ -109,242 +109,21 @@ keychain = startSketchOn("XY")
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(thickness, %)
|
|> extrude(thickness, %)
|
||||||
|
|
||||||
// generated from /home/paultag/Downloads/zma-logomark.svg
|
keychain1 = startSketchOn("XY")
|
||||||
fn svg = (surface, origin, depth) => {
|
|> startProfileAt([0, 0], %)
|
||||||
let a0 = surface |> startProfileAt([origin[0] + 45.430427, origin[1] + -14.627736], %)
|
|> lineTo([width, 0], %)
|
||||||
|> bezierCurve({
|
|> lineTo([width, height], %)
|
||||||
control1: [ 0, 0.764157 ],
|
|> lineTo([0, height], %)
|
||||||
control2: [ 0, 1.528314 ],
|
|> close(%)
|
||||||
to: [ 0, 2.292469 ]
|
|> extrude(thickness, %)
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -3.03202, 0 ],
|
|
||||||
control2: [ -6.064039, 0 ],
|
|
||||||
to: [ -9.09606, 0 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0, -1.077657 ],
|
|
||||||
control2: [ 0, -2.155312 ],
|
|
||||||
to: [ 0, -3.232969 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 2.741805, 0 ],
|
|
||||||
control2: [ 5.483613, 0 ],
|
|
||||||
to: [ 8.225417, 0 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -2.740682, -2.961815 ],
|
|
||||||
control2: [ -5.490342, -5.925794 ],
|
|
||||||
to: [ -8.225417, -8.886255 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0, -0.723995 ],
|
|
||||||
control2: [ 0, -1.447988 ],
|
|
||||||
to: [ 0, -2.171981 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.712124, 0.05061 ],
|
|
||||||
control2: [ 1.511636, -0.09877 ],
|
|
||||||
to: [ 2.172096, 0.07005 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.68573, 0.740811 ],
|
|
||||||
control2: [ 1.371459, 1.481622 ],
|
|
||||||
to: [ 2.057187, 2.222436 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0, -0.76416 ],
|
|
||||||
control2: [ 0, -1.52832 ],
|
|
||||||
to: [ 0, -2.29248 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 3.032013, 0 ],
|
|
||||||
control2: [ 6.064026, 0 ],
|
|
||||||
to: [ 9.096038, 0 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0, 1.077657 ],
|
|
||||||
control2: [ 0, 2.155314 ],
|
|
||||||
to: [ 0, 3.232973 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -2.741312, 0 ],
|
|
||||||
control2: [ -5.482623, 0 ],
|
|
||||||
to: [ -8.223936, 0 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 2.741313, 2.961108 ],
|
|
||||||
control2: [ 5.482624, 5.922216 ],
|
|
||||||
to: [ 8.223936, 8.883325 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0, 0.724968 ],
|
|
||||||
control2: [ 0, 1.449938 ],
|
|
||||||
to: [ 0, 2.174907 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -0.712656, -0.05145 ],
|
|
||||||
control2: [ -1.512554, 0.09643 ],
|
|
||||||
to: [ -2.173592, -0.07298 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -0.685222, -0.739834 ],
|
|
||||||
control2: [ -1.370445, -1.479669 ],
|
|
||||||
to: [ -2.055669, -2.219505 ]
|
|
||||||
}, %)
|
|
||||||
|> close(%)
|
|
||||||
|> extrude(depth, %)
|
|
||||||
|
|
||||||
let a1 = surface |> startProfileAt([origin[0] + 57.920488, origin[1] + -15.244943], %)
|
keychain2 = startSketchOn("XY")
|
||||||
|> bezierCurve({
|
|> startProfileAt([0, 0], %)
|
||||||
control1: [ -2.78904, 0.106635 ],
|
|> lineTo([width, 0], %)
|
||||||
control2: [ -5.052548, -2.969529 ],
|
|> lineTo([width, height], %)
|
||||||
to: [ -4.055141, -5.598369 ]
|
|> lineTo([0, height], %)
|
||||||
}, %)
|
|> close(%)
|
||||||
|> bezierCurve({
|
|> extrude(thickness, %)
|
||||||
control1: [ 0.841523, -0.918736 ],
|
|
||||||
control2: [ 0.439412, -1.541892 ],
|
|
||||||
to: [ -0.368488, -2.214378 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -0.418245, -0.448461 ],
|
|
||||||
control2: [ -0.836489, -0.896922 ],
|
|
||||||
to: [ -1.254732, -1.345384 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -2.76806, 2.995359 ],
|
|
||||||
control2: [ -2.32667, 8.18409 ],
|
|
||||||
to: [ 0.897655, 10.678932 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 2.562822, 2.186098 ],
|
|
||||||
control2: [ 6.605111, 2.28043 ],
|
|
||||||
to: [ 9.271202, 0.226476 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -0.743744, -0.797465 ],
|
|
||||||
control2: [ -1.487487, -1.594932 ],
|
|
||||||
to: [ -2.231232, -2.392397 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -0.672938, 0.421422 ],
|
|
||||||
control2: [ -1.465362, 0.646946 ],
|
|
||||||
to: [ -2.259264, 0.64512 ]
|
|
||||||
}, %)
|
|
||||||
|> close(%)
|
|
||||||
|> extrude(depth, %)
|
|
||||||
|
|
||||||
let a2 = surface |> startProfileAt([origin[0] + 62.19406300000001, origin[1] + -19.500698999999997], %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.302938, 1.281141 ],
|
|
||||||
control2: [ -1.53575, 2.434288 ],
|
|
||||||
to: [ -0.10908, 3.279477 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.504637, 0.54145 ],
|
|
||||||
control2: [ 1.009273, 1.082899 ],
|
|
||||||
to: [ 1.513909, 1.624348 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 2.767778, -2.995425 ],
|
|
||||||
control2: [ 2.327135, -8.184384 ],
|
|
||||||
to: [ -0.897661, -10.679047 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -2.562947, -2.186022 ],
|
|
||||||
control2: [ -6.604089, -2.279606 ],
|
|
||||||
to: [ -9.271196, -0.227813 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.744231, 0.797952 ],
|
|
||||||
control2: [ 1.488461, 1.595904 ],
|
|
||||||
to: [ 2.232692, 2.393856 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 2.302377, -1.564629 ],
|
|
||||||
control2: [ 5.793126, -0.15358 ],
|
|
||||||
to: [ 6.396577, 2.547372 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.08981, 0.346302 ],
|
|
||||||
control2: [ 0.134865, 0.704078 ],
|
|
||||||
to: [ 0.13476, 1.061807 ]
|
|
||||||
}, %)
|
|
||||||
|> close(%)
|
|
||||||
|> extrude(depth, %)
|
|
||||||
|
|
||||||
let a3 = surface |> startProfileAt([origin[0] + 74.124866, origin[1] + -15.244943], %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -2.78904, 0.106635 ],
|
|
||||||
control2: [ -5.052549, -2.969529 ],
|
|
||||||
to: [ -4.055142, -5.598369 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.841527, -0.918738 ],
|
|
||||||
control2: [ 0.43941, -1.541892 ],
|
|
||||||
to: [ -0.368497, -2.214367 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -0.418254, -0.448466 ],
|
|
||||||
control2: [ -0.836507, -0.896931 ],
|
|
||||||
to: [ -1.254761, -1.345395 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -2.768019, 2.995371 ],
|
|
||||||
control2: [ -2.326624, 8.184088 ],
|
|
||||||
to: [ 0.897678, 10.678932 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 2.56289, 2.186191 ],
|
|
||||||
control2: [ 6.60516, 2.280307 ],
|
|
||||||
to: [ 9.271371, 0.226476 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -0.743808, -0.797465 ],
|
|
||||||
control2: [ -1.487616, -1.594932 ],
|
|
||||||
to: [ -2.231424, -2.392397 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -0.672916, 0.421433 ],
|
|
||||||
control2: [ -1.465344, 0.646926 ],
|
|
||||||
to: [ -2.259225, 0.64512 ]
|
|
||||||
}, %)
|
|
||||||
|> close(%)
|
|
||||||
|> extrude(depth, %)
|
|
||||||
|
|
||||||
let a4 = surface |> startProfileAt([origin[0] + 77.57333899999998, origin[1] + -16.989262999999998], %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.743298, 0.797463 ],
|
|
||||||
control2: [ 1.486592, 1.594926 ],
|
|
||||||
to: [ 2.229888, 2.392389 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 2.767827, -2.995393 ],
|
|
||||||
control2: [ 2.327103, -8.184396 ],
|
|
||||||
to: [ -0.897672, -10.679047 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ -2.562939, -2.186037 ],
|
|
||||||
control2: [ -6.604077, -2.279589 ],
|
|
||||||
to: [ -9.271185, -0.227813 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.744243, 0.797952 ],
|
|
||||||
control2: [ 1.488486, 1.595904 ],
|
|
||||||
to: [ 2.232729, 2.393856 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 2.302394, -1.564623 ],
|
|
||||||
control2: [ 5.793201, -0.153598 ],
|
|
||||||
to: [ 6.396692, 2.547372 ]
|
|
||||||
}, %)
|
|
||||||
|> bezierCurve({
|
|
||||||
control1: [ 0.32074, 1.215468 ],
|
|
||||||
control2: [ 0.06159, 2.564765 ],
|
|
||||||
to: [ -0.690452, 3.573243 ]
|
|
||||||
}, %)
|
|
||||||
|> close(%)
|
|
||||||
|> extrude(depth, %)
|
|
||||||
|
|
||||||
box = startSketchOn('XY')
|
box = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
@ -354,7 +133,7 @@ box = startSketchOn('XY')
|
|||||||
|> 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], %)
|
||||||
@ -364,18 +143,12 @@ box = startSketchOn('XY')
|
|||||||
axis: revolveAxis,
|
axis: revolveAxis,
|
||||||
angle: 90
|
angle: 90
|
||||||
}, %)
|
}, %)
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.0, 0.0], %)
|
||||||
|
|> xLine(0.0, %)
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
`
|
||||||
svg(startSketchOn(keychain, 'end'), [-33, 32], -thickness)
|
|
||||||
|
|
||||||
startSketchOn(keychain, 'end')
|
|
||||||
|> circle({ center: [
|
|
||||||
width / 2,
|
|
||||||
height - (keychainHoleSize + 1.5)
|
|
||||||
], radius: keychainHoleSize }, %)
|
|
||||||
|> extrude(-thickness, %)`
|
|
||||||
|
|
||||||
export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `thing = 1`
|
export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `thing = 1`
|
||||||
|
@ -1,29 +1,16 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { commonPoints, getUtils, setup, tearDown } from './test-utils'
|
import { commonPoints, 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('Test network and connection issues', () => {
|
test.describe('Test network and connection issues', () => {
|
||||||
test('simulate network down and network little widget', async ({
|
test('simulate network down and network little widget', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
|
||||||
test.skip(
|
|
||||||
browserName === 'webkit',
|
|
||||||
'Skip on Safari until `window.tearDown` is working there'
|
|
||||||
)
|
|
||||||
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()
|
||||||
|
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
|
|
||||||
@ -62,7 +49,7 @@ test.describe('Test network and connection issues', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Expect the network to be down
|
// Expect the network to be down
|
||||||
await expect(networkToggle).toContainText('Offline')
|
await expect(networkToggle).toContainText('Problem')
|
||||||
|
|
||||||
// Click the network widget
|
// Click the network widget
|
||||||
await networkWidget.click()
|
await networkWidget.click()
|
||||||
@ -93,26 +80,19 @@ test.describe('Test network and connection issues', () => {
|
|||||||
|
|
||||||
test('Engine disconnect & reconnect in sketch mode', async ({
|
test('Engine disconnect & reconnect in sketch mode', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
||||||
test.skip(
|
|
||||||
browserName === 'webkit',
|
|
||||||
'Skip on Safari until `window.tearDown` is working there'
|
|
||||||
)
|
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
|
|
||||||
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 u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).not.toBeDisabled({ timeout: 15000 })
|
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
@ -132,7 +112,7 @@ test.describe('Test network and connection issues', () => {
|
|||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
@ -140,8 +120,8 @@ test.describe('Test network and connection issues', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> xLine(${commonPoints.num1}, %)`)
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
|
|
||||||
// Expect the network to be up
|
// Expect the network to be up
|
||||||
await expect(networkToggle).toContainText('Connected')
|
await expect(networkToggle).toContainText('Connected')
|
||||||
@ -156,7 +136,7 @@ test.describe('Test network and connection issues', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Expect the network to be down
|
// Expect the network to be down
|
||||||
await expect(networkToggle).toContainText('Offline')
|
await expect(networkToggle).toContainText('Problem')
|
||||||
|
|
||||||
// Ensure we are not in sketch mode
|
// Ensure we are not in sketch mode
|
||||||
await expect(
|
await expect(
|
||||||
|