Compare commits
48 Commits
v0.32.0
...
move-tests
| Author | SHA1 | Date | |
|---|---|---|---|
| ff64367729 | |||
| c440bbc0fe | |||
| c2bb81ad1f | |||
| 82ec52b317 | |||
| f5a14166a1 | |||
| 6ac8cef180 | |||
| f0c8dbd5a2 | |||
| c20ce60c9a | |||
| f2b8e66952 | |||
| cdd73e952a | |||
| edbff28296 | |||
| 006fcd7490 | |||
| 4795de789d | |||
| 1a04a4dcfb | |||
| 119cacf6a8 | |||
| 12b00aca34 | |||
| 5c56f94fbb | |||
| 329e60dda8 | |||
| 2844f4c4d6 | |||
| 0c48006793 | |||
| 534d1ddecc | |||
| 4f5766d423 | |||
| 253556d6c7 | |||
| 10019a180d | |||
| 728314ccda | |||
| 7e229099a0 | |||
| da217b6c1a | |||
| ff4e1a6c73 | |||
| a7c2548645 | |||
| ccfc592b62 | |||
| b3cc43ae2f | |||
| f061a9a15f | |||
| 2b92f00d3c | |||
| 4b0823dbbf | |||
| 08a4016fda | |||
| cb56fc7555 | |||
| d7d822cc11 | |||
| 8e450378c3 | |||
| fd39fcdb25 | |||
| 24011dd100 | |||
| a9b78fb2a4 | |||
| 765e27c02b | |||
| 05baf9884d | |||
| 1ced492deb | |||
| 804124b07f | |||
| b3112025b9 | |||
| 898b4ed016 | |||
| 4b21a5b667 |
@ -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."
|
||||||
|
|||||||
22
.github/workflows/cargo-test.yml
vendored
@ -2,8 +2,28 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
paths:
|
||||||
|
- 'src/wasm-lib/**.rs'
|
||||||
|
- 'src/wasm-lib/**.hbs'
|
||||||
|
- 'src/wasm-lib/**.gen'
|
||||||
|
- 'src/wasm-lib/**.snap'
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
- '**/Cargo.lock'
|
||||||
|
- '**/rust-toolchain.toml'
|
||||||
|
- 'src/wasm-lib/**.kcl'
|
||||||
|
- .github/workflows/cargo-test.yml
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'src/wasm-lib/**.rs'
|
||||||
|
- 'src/wasm-lib/**.hbs'
|
||||||
|
- 'src/wasm-lib/**.gen'
|
||||||
|
- 'src/wasm-lib/**.snap'
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
- '**/Cargo.lock'
|
||||||
|
- '**/rust-toolchain.toml'
|
||||||
|
- 'src/wasm-lib/**.kcl'
|
||||||
|
- .github/workflows/cargo-test.yml
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
concurrency:
|
concurrency:
|
||||||
@ -51,7 +71,7 @@ jobs:
|
|||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
RUST_MIN_STACK: 10485760000
|
RUST_MIN_STACK: 10485760000
|
||||||
- name: Upload to codecov.io
|
- name: Upload to codecov.io
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
token: ${{secrets.CODECOV_TOKEN}}
|
token: ${{secrets.CODECOV_TOKEN}}
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
|||||||
154
.github/workflows/e2e-tests.yml
vendored
@ -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 }}
|
||||||
@ -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@v7
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
name: wasm-bundle
|
|
||||||
workflow: build-and-store-wasm.yml
|
|
||||||
branch: main
|
|
||||||
path: src/wasm-lib/pkg
|
|
||||||
- name: copy wasm blob
|
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
|
||||||
shell: bash
|
|
||||||
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
|
||||||
continue-on-error: true
|
|
||||||
- name: Setup Rust
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
- name: Cache Wasm (because rust diff)
|
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: './src/wasm-lib'
|
|
||||||
- name: OR Cache Wasm (because wasm cache failed)
|
|
||||||
if: steps.download-wasm.outcome == 'failure'
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: './src/wasm-lib'
|
|
||||||
- name: install good sed
|
|
||||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
brew install gnu-sed
|
|
||||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
|
||||||
- name: Install vector
|
|
||||||
if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
|
||||||
chmod +x /tmp/vector.sh
|
|
||||||
/tmp/vector.sh -y -no-modify-path
|
|
||||||
mkdir -p /tmp/vector
|
|
||||||
cp .github/workflows/vector.toml /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
|
|
||||||
cat /tmp/vector.toml
|
|
||||||
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
|
|
||||||
- name: Build Wasm (because rust diff)
|
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
|
||||||
shell: bash
|
|
||||||
run: yarn build:wasm
|
|
||||||
- name: OR Build Wasm (because wasm cache failed)
|
|
||||||
if: steps.download-wasm.outcome == 'failure'
|
|
||||||
shell: bash
|
|
||||||
run: yarn build:wasm
|
|
||||||
- name: build electron
|
|
||||||
shell: bash
|
|
||||||
run: yarn tron:package
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
|
||||||
path: test-results/
|
|
||||||
- name: Run electron tests (with retries)
|
|
||||||
id: retry
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
.github/ci-cd-scripts/playwright-electron.sh ${{ matrix.os }}
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
FAIL_ON_CONSOLE_ERRORS: true
|
|
||||||
NODE_ENV: development
|
|
||||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
|
||||||
VITE_KC_SKIP_AUTH: true
|
|
||||||
IS_UBUNTU: ${{ startsWith(matrix.os, 'ubuntu') && 'true' || 'false' }}
|
|
||||||
#DEBUG: 'pw:browser*'
|
|
||||||
- name: send to axiom
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) && !startsWith(matrix.os, 'windows') }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
node playwrightProcess.mjs | tee /tmp/github-actions.log
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
|
||||||
with:
|
|
||||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
|
||||||
path: test-results/
|
|
||||||
include-hidden-files: true
|
|
||||||
retention-days: 30
|
|
||||||
overwrite: true
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
|
||||||
with:
|
|
||||||
name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }}
|
|
||||||
path: playwright-report/
|
|
||||||
include-hidden-files: true
|
|
||||||
retention-days: 30
|
|
||||||
overwrite: true
|
|
||||||
|
|||||||
17
README.md
@ -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.
|
||||||
|
|||||||
@ -30,7 +30,6 @@ layout: manual
|
|||||||
* [`assertLessThan`](kcl/assertLessThan)
|
* [`assertLessThan`](kcl/assertLessThan)
|
||||||
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
||||||
* [`atan`](kcl/atan)
|
* [`atan`](kcl/atan)
|
||||||
* [`atan2`](kcl/atan2)
|
|
||||||
* [`bezierCurve`](kcl/bezierCurve)
|
* [`bezierCurve`](kcl/bezierCurve)
|
||||||
* [`ceil`](kcl/ceil)
|
* [`ceil`](kcl/ceil)
|
||||||
* [`chamfer`](kcl/chamfer)
|
* [`chamfer`](kcl/chamfer)
|
||||||
@ -103,7 +102,6 @@ layout: manual
|
|||||||
* [`startProfileAt`](kcl/startProfileAt)
|
* [`startProfileAt`](kcl/startProfileAt)
|
||||||
* [`startSketchAt`](kcl/startSketchAt)
|
* [`startSketchAt`](kcl/startSketchAt)
|
||||||
* [`startSketchOn`](kcl/startSketchOn)
|
* [`startSketchOn`](kcl/startSketchOn)
|
||||||
* [`sweep`](kcl/sweep)
|
|
||||||
* [`tan`](kcl/tan)
|
* [`tan`](kcl/tan)
|
||||||
* [`tangentToEnd`](kcl/tangentToEnd)
|
* [`tangentToEnd`](kcl/tangentToEnd)
|
||||||
* [`tangentialArc`](kcl/tangentialArc)
|
* [`tangentialArc`](kcl/tangentialArc)
|
||||||
|
|||||||
@ -43,7 +43,7 @@ fn sum(arr) {
|
|||||||
|
|
||||||
/* The above is basically like this pseudo-code:
|
/* The above is basically like this pseudo-code:
|
||||||
fn sum(arr):
|
fn sum(arr):
|
||||||
sumSoFar = 0
|
let sumSoFar = 0
|
||||||
for i in arr:
|
for i in arr:
|
||||||
sumSoFar = add(sumSoFar, i)
|
sumSoFar = add(sumSoFar, i)
|
||||||
return sumSoFar */
|
return sumSoFar */
|
||||||
@ -96,14 +96,14 @@ fn decagon(radius) {
|
|||||||
|
|
||||||
/* The `decagon` above is basically like this pseudo-code:
|
/* The `decagon` above is basically like this pseudo-code:
|
||||||
fn decagon(radius):
|
fn decagon(radius):
|
||||||
stepAngle = (1/10) * tau()
|
let stepAngle = (1/10) * tau()
|
||||||
startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
||||||
|
|
||||||
// Here's the reduce part.
|
// Here's the reduce part.
|
||||||
partialDecagon = startOfDecagonSketch
|
let partialDecagon = startOfDecagonSketch
|
||||||
for i in [1..10]:
|
for i in [1..10]:
|
||||||
x = cos(stepAngle * i) * radius
|
let x = cos(stepAngle * i) * radius
|
||||||
y = sin(stepAngle * i) * radius
|
let y = sin(stepAngle * i) * radius
|
||||||
partialDecagon = lineTo([x, y], partialDecagon)
|
partialDecagon = lineTo([x, y], partialDecagon)
|
||||||
fullDecagon = partialDecagon // it's now full
|
fullDecagon = partialDecagon // it's now full
|
||||||
return fullDecagon */
|
return fullDecagon */
|
||||||
|
|||||||
8331
docs/kcl/std.json
@ -12,10 +12,5 @@ KCL value for an optional parameter which was not given an argument. (remember,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
title: "SweepData"
|
|
||||||
excerpt: "Data for a sweep."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
Data for a sweep.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No |
|
|
||||||
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
|
||||||
| `tolerance` |`number`| Tolerance for the sweep operation. | No |
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,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()
|
||||||
@ -62,11 +49,11 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
await expect(codePaneButtonHolder).toContainText('notification')
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Opening and closing the code pane will consistently show error diagnostics', async ({
|
test.skip('Opening and closing the code pane will consistently show error diagnostics', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
|
editor,
|
||||||
}) => {
|
}) => {
|
||||||
await page.goto('http://localhost:3000')
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Load the app with the working starter code
|
// Load the app with the working starter code
|
||||||
@ -74,8 +61,8 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
}, bracket)
|
}, bracket)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 900 })
|
await page.setBodyDimensions({ width: 1200, height: 900 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -91,8 +78,9 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
await expect(codePaneButtonHolder).not.toContainText('notification')
|
await expect(codePaneButtonHolder).not.toContainText('notification')
|
||||||
|
|
||||||
// Delete a character to break the KCL
|
// Delete a character to break the KCL
|
||||||
await u.openKclCodePanel()
|
await editor.openPane()
|
||||||
await page.getByText('thickness, bracketLeg1Sketch)').click()
|
await editor.scrollToText('thickness, bracketLeg1Sketch)')
|
||||||
|
await page.getByText('extrude(thickness, bracketLeg1Sketch)').click()
|
||||||
await page.keyboard.press('Backspace')
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
// Ensure that a badge appears on the button
|
// Ensure that a badge appears on the button
|
||||||
@ -116,7 +104,10 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
// Open the code pane
|
// Open the code pane
|
||||||
await u.openKclCodePanel()
|
await editor.openPane()
|
||||||
|
|
||||||
|
// Go to our problematic code again (missing closing paren!)
|
||||||
|
await editor.scrollToText('extrude(thickness, bracketLeg1Sketch')
|
||||||
|
|
||||||
// Ensure that a badge appears on the button
|
// Ensure that a badge appears on the button
|
||||||
await expect(codePaneButtonHolder).toContainText('notification')
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
@ -129,59 +120,58 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
await expect(page.locator('.cm-tooltip').first()).toBeVisible()
|
await expect(page.locator('.cm-tooltip').first()).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('When error is not in view you can click the badge to scroll to it', async ({
|
test.fixme(
|
||||||
page,
|
'When error is not in view you can click the badge to scroll to it',
|
||||||
}) => {
|
async ({ page, homePage, context }) => {
|
||||||
const u = await getUtils(page)
|
// Load the app with the working starter code
|
||||||
|
await context.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||||
|
|
||||||
// Load the app with the working starter code
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await page.addInitScript((code) => {
|
await homePage.goToModelingScene()
|
||||||
localStorage.setItem('persistCode', code)
|
|
||||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.waitForTimeout(1000)
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await page.waitForTimeout(1000)
|
// Ensure badge is present
|
||||||
|
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||||
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
|
|
||||||
// Ensure badge is present
|
// Ensure we have no errors in the gutter, since error out of view.
|
||||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
await expect(codePaneButtonHolder).toContainText('notification')
|
|
||||||
|
|
||||||
// Ensure we have no errors in the gutter, since error out of view.
|
// Click the badge.
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
const badge = page.locator('#code-badge')
|
||||||
|
await expect(badge).toBeVisible()
|
||||||
|
await badge.click()
|
||||||
|
|
||||||
// Click the badge.
|
// Ensure we have an error diagnostic.
|
||||||
const badge = page.locator('#code-badge')
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
await expect(badge).toBeVisible()
|
|
||||||
await badge.click()
|
|
||||||
|
|
||||||
// Ensure we have an error diagnostic.
|
// Hover over the error to see the error message
|
||||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
await page.hover('.cm-lint-marker-error')
|
||||||
|
await expect(
|
||||||
// Hover over the error to see the error message
|
page
|
||||||
await page.hover('.cm-lint-marker-error')
|
.getByText(
|
||||||
await expect(
|
'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
|
||||||
page
|
)
|
||||||
.getByText(
|
.first()
|
||||||
'sketch profile must lie entirely on one side of the revolution axis'
|
).toBeVisible()
|
||||||
)
|
}
|
||||||
.first()
|
)
|
||||||
).toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
|
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
|
||||||
|
context,
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
// Load the app with the working starter code
|
// Load the app with the working starter code
|
||||||
await page.addInitScript((code) => {
|
await context.addInitScript((code) => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
@ -241,32 +231,29 @@ extrude001 = extrude(5, sketch001)`
|
|||||||
test(
|
test(
|
||||||
'Opening multiple panes persists when switching projects',
|
'Opening multiple panes persists when switching projects',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
// Setup multiple projects.
|
// Setup multiple projects.
|
||||||
const { electronApp, page } = await setupElectron({
|
await context.folderSetupFn(async (dir) => {
|
||||||
testInfo,
|
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||||
folderSetupFn: async (dir) => {
|
const bracketDir = join(dir, 'bracket')
|
||||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
await Promise.all([
|
||||||
const bracketDir = join(dir, 'bracket')
|
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||||
await Promise.all([
|
fsp.mkdir(bracketDir, { recursive: true }),
|
||||||
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
])
|
||||||
fsp.mkdir(bracketDir, { recursive: true }),
|
await Promise.all([
|
||||||
])
|
fsp.copyFile(
|
||||||
await Promise.all([
|
executorInputPath('router-template-slate.kcl'),
|
||||||
fsp.copyFile(
|
join(routerTemplateDir, 'main.kcl')
|
||||||
executorInputPath('router-template-slate.kcl'),
|
),
|
||||||
join(routerTemplateDir, 'main.kcl')
|
fsp.copyFile(
|
||||||
),
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
fsp.copyFile(
|
join(bracketDir, 'main.kcl')
|
||||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
),
|
||||||
join(bracketDir, 'main.kcl')
|
])
|
||||||
),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await test.step('Opening the bracket project should load', async () => {
|
await test.step('Opening the bracket project should load', async () => {
|
||||||
await expect(page.getByText('bracket')).toBeVisible()
|
await expect(page.getByText('bracket')).toBeVisible()
|
||||||
@ -309,30 +296,21 @@ test(
|
|||||||
await expect(page.locator('#variables-pane')).toBeVisible()
|
await expect(page.locator('#variables-pane')).toBeVisible()
|
||||||
await expect(page.locator('#logs-pane')).toBeVisible()
|
await expect(page.locator('#logs-pane')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'external change of file contents are reflected in editor',
|
'external change of file contents are reflected in editor',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const PROJECT_DIR_NAME = 'lee-was-here'
|
const PROJECT_DIR_NAME = 'lee-was-here'
|
||||||
const {
|
const { dir: projectsDir } = await context.folderSetupFn(async (dir) => {
|
||||||
electronApp,
|
const aProjectDir = join(dir, PROJECT_DIR_NAME)
|
||||||
page,
|
await fsp.mkdir(aProjectDir, { recursive: true })
|
||||||
dir: projectsDir,
|
|
||||||
} = await setupElectron({
|
|
||||||
testInfo,
|
|
||||||
folderSetupFn: async (dir) => {
|
|
||||||
const aProjectDir = join(dir, PROJECT_DIR_NAME)
|
|
||||||
await fsp.mkdir(aProjectDir, { recursive: true })
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await test.step('Open the project', async () => {
|
await test.step('Open the project', async () => {
|
||||||
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
|
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
|
||||||
@ -351,7 +329,5 @@ test(
|
|||||||
)
|
)
|
||||||
await u.editorTextMatches(content)
|
await u.editorTextMatches(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,37 +1,30 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Command bar tests', () => {
|
test.describe('Command bar tests', () => {
|
||||||
test('Extrude from command bar selects extrude line after', async ({
|
test('Extrude from command bar selects extrude line after', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XY')
|
`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> xLine(-20, %)
|
|> xLine(-20, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
@ -52,24 +45,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()
|
||||||
@ -87,16 +80,16 @@ extrude001 = extrude(-10, sketch001)`
|
|||||||
await page.keyboard.press('Enter') // submit
|
await page.keyboard.press('Enter') // submit
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-activeLine')).toContainText(
|
await expect(page.locator('.cm-activeLine')).toContainText(
|
||||||
`fillet({ radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] }, %)`
|
`fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await homePage.goToModelingScene()
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
@ -153,7 +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,106 +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()
|
||||||
|
|
||||||
test('ensure we use the cache, and do not re-execute', async ({ page }) => {
|
await expect(page.locator('.cm-content'))
|
||||||
const u = await getUtils(page)
|
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await u.codeLocator.click()
|
|
||||||
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
|
||||||
// Ensure we execute the first time.
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await expect(
|
|
||||||
page.locator('[data-receive-command-type="scene_clear_all"]')
|
|
||||||
).toHaveCount(2)
|
|
||||||
await expect(
|
|
||||||
page.locator('[data-message-type="execution-done"]')
|
|
||||||
).toHaveCount(2)
|
|
||||||
|
|
||||||
// Add whitespace to the end of the code.
|
|
||||||
await u.codeLocator.click()
|
|
||||||
await page.keyboard.press('ArrowUp')
|
|
||||||
await page.keyboard.press('ArrowUp')
|
|
||||||
await page.keyboard.press('ArrowUp')
|
|
||||||
await page.keyboard.press('ArrowUp')
|
|
||||||
await page.keyboard.press('Home')
|
|
||||||
await page.keyboard.type(' ')
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
await page.keyboard.type(' ')
|
|
||||||
|
|
||||||
// Ensure we don't execute the second time.
|
|
||||||
await u.openDebugPanel()
|
|
||||||
// Make sure we didn't clear the scene.
|
|
||||||
await expect(
|
|
||||||
page.locator('[data-message-type="execution-done"]')
|
|
||||||
).toHaveCount(3)
|
|
||||||
await expect(
|
|
||||||
page.locator('[data-receive-command-type="scene_clear_all"]')
|
|
||||||
).toHaveCount(2)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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"]')
|
||||||
@ -180,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()
|
||||||
@ -196,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.
|
||||||
@ -269,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()
|
||||||
@ -313,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()
|
||||||
@ -346,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"]')
|
||||||
@ -398,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()
|
||||||
@ -414,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()
|
||||||
@ -454,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()
|
||||||
@ -492,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
|
||||||
topAng = 30
|
const topAng = 30
|
||||||
bottomAng = 25
|
const bottomAng = 25
|
||||||
*/
|
*/
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type('$ error')
|
await page.keyboard.type('$ error')
|
||||||
|
|
||||||
@ -519,14 +478,12 @@ test.describe('Editor tests', () => {
|
|||||||
await page.keyboard.type('bottomAng = 25')
|
await page.keyboard.type('bottomAng = 25')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// error in gutter
|
// error in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
|
||||||
// error text on hover
|
// error text on hover
|
||||||
await page.hover('.cm-lint-marker-error')
|
await page.hover('.cm-lint-marker-error')
|
||||||
await expect(
|
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
|
||||||
page.getByText('Tag names must not be empty').first()
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
// select the line that's causing the error and delete it
|
// select the line that's causing the error and delete it
|
||||||
await page.getByText('$ error').click()
|
await page.getByText('$ error').click()
|
||||||
@ -565,106 +522,108 @@ test.describe('Editor tests', () => {
|
|||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO currently multiple source ranges are not supported
|
test.fixme(
|
||||||
test.skip('error with 2 source ranges gets 2 diagnostics', async ({
|
'error with 2 source ranges gets 2 diagnostics',
|
||||||
page,
|
async ({ page, homePage }) => {
|
||||||
}) => {
|
const u = await getUtils(page)
|
||||||
const u = await getUtils(page)
|
await page.addInitScript(async () => {
|
||||||
await page.addInitScript(async () => {
|
localStorage.setItem(
|
||||||
localStorage.setItem(
|
'persistCode',
|
||||||
'persistCode',
|
`length = .750
|
||||||
`length = .750
|
width = 0.500
|
||||||
width = 0.500
|
height = 0.500
|
||||||
height = 0.500
|
dia = 4
|
||||||
dia = 4
|
|
||||||
|
fn squareHole = (l, w) => {
|
||||||
|
squareHoleSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-width / 2, -length / 2], %)
|
||||||
|
|> lineTo([width / 2, -length / 2], %)
|
||||||
|
|> lineTo([width / 2, length / 2], %)
|
||||||
|
|> lineTo([-width / 2, length / 2], %)
|
||||||
|
|> close(%)
|
||||||
|
return squareHoleSketch
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
fn squareHole = (l, w) => {
|
await homePage.goToModelingScene()
|
||||||
squareHoleSketch = startSketchOn('XY')
|
await u.waitForPageLoad()
|
||||||
|> startProfileAt([-width / 2, -length / 2], %)
|
await page.waitForTimeout(1000)
|
||||||
|> lineTo([width / 2, -length / 2], %)
|
|
||||||
|> lineTo([width / 2, length / 2], %)
|
|
||||||
|> lineTo([-width / 2, length / 2], %)
|
|
||||||
|> close(%)
|
|
||||||
return squareHoleSketch
|
|
||||||
}
|
|
||||||
`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
// check no error to begin with
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
// check no error to begin with
|
// Click on the bottom of the code editor to add a new line
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
||||||
|
|> circle({ center: [0, 0], radius: dia/2 }, %)
|
||||||
|
|> hole(squareHole(length, width, height), %)
|
||||||
|
|> extrude(height, %)`)
|
||||||
|
|
||||||
// Click on the bottom of the code editor to add a new line
|
// error in gutter
|
||||||
await u.codeLocator.click()
|
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.hover('.cm-lint-marker-error:first-child')
|
||||||
await page.keyboard.press('ArrowDown')
|
await expect(
|
||||||
await page.keyboard.press('ArrowDown')
|
page.getByText('Expected 2 arguments, got 3').first()
|
||||||
await page.keyboard.press('ArrowDown')
|
).toBeVisible()
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('ArrowDown')
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
|
||||||
|> circle({ center = [0, 0], radius = dia/2 }, %)
|
|
||||||
|> hole(squareHole(length, width, height), %)
|
|
||||||
|> extrude(height, %)`)
|
|
||||||
|
|
||||||
// error in gutter
|
// Make sure there are two diagnostics
|
||||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
||||||
await page.hover('.cm-lint-marker-error:first-child')
|
}
|
||||||
await expect(
|
)
|
||||||
page.getByText('Expected 2 arguments, got 3').first()
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
// Make sure there are two diagnostics
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
|
||||||
})
|
|
||||||
test('if your kcl gets an error from the engine it is inlined', async ({
|
test('if your kcl gets an error from the engine it is inlined', async ({
|
||||||
|
context,
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
await context.addInitScript(async () => {
|
||||||
await page.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`box = startSketchOn('XY')
|
`box = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([0, 10], %)
|
|> line([0, 10], %)
|
||||||
|> line([10, 0], %)
|
|> line([10, 0], %)
|
||||||
|> line([0, -10], %, $revolveAxis)
|
|> line([0, -10], %, $revolveAxis)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(10, %)
|
|> extrude(10, %)
|
||||||
|
|
||||||
sketch001 = startSketchOn(box, revolveAxis)
|
sketch001 = startSketchOn(box, revolveAxis)
|
||||||
|> startProfileAt([5, 10], %)
|
|> startProfileAt([5, 10], %)
|
||||||
|> line([0, -10], %)
|
|> line([0, -10], %)
|
||||||
|> line([2, 0], %)
|
|> line([2, 0], %)
|
||||||
|> line([0, -10], %)
|
|> line([0, -10], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> revolve({
|
|> revolve({
|
||||||
axis = revolveAxis,
|
axis: revolveAxis,
|
||||||
angle = 90
|
angle: 90
|
||||||
}, %)
|
}, %)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await page.goto('/')
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
|
|
||||||
@ -675,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
|
||||||
@ -742,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.
|
||||||
@ -816,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()
|
||||||
@ -888,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()
|
||||||
@ -937,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
|
||||||
|
|
||||||
@ -951,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)
|
||||||
@ -991,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')
|
||||||
@ -1005,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')
|
||||||
@ -1018,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')
|
||||||
@ -1033,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)
|
||||||
@ -1115,8 +1082,6 @@ test.describe('Editor tests', () => {
|
|||||||
})
|
})
|
||||||
.toBeGreaterThan(15)
|
.toBeGreaterThan(15)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export class EditorFixture {
|
|||||||
reConstruct = (page: Page) => {
|
reConstruct = (page: Page) => {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
|
||||||
this.codeContent = page.locator('.cm-content')
|
this.codeContent = page.locator('.cm-content[data-language="kcl"]')
|
||||||
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
||||||
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
|
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
|
||||||
this.activeLine = this.page.locator('.cm-activeLine')
|
this.activeLine = this.page.locator('.cm-activeLine')
|
||||||
@ -54,13 +54,13 @@ export class EditorFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!shouldNormalise) {
|
if (!shouldNormalise) {
|
||||||
const expectStart = expect(this.codeContent)
|
const expectStart = expect.poll(() => this.codeContent.textContent())
|
||||||
if (not) {
|
if (not) {
|
||||||
const result = await expectStart.not.toContainText(code, { timeout })
|
const result = await expectStart.not.toContain(code)
|
||||||
await resetPane()
|
await resetPane()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
const result = await expectStart.toContainText(code, { timeout })
|
const result = await expectStart.toContain(code)
|
||||||
await resetPane()
|
await resetPane()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -147,4 +147,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,22 @@ export class AuthenticatedTronApp {
|
|||||||
appSettings?: Partial<SaveSettingsPayload>
|
appSettings?: Partial<SaveSettingsPayload>
|
||||||
} = { fixtures: {} }
|
} = { fixtures: {} }
|
||||||
) {
|
) {
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page, context, dir, options } = await setupElectron({
|
||||||
testInfo: this.testInfo,
|
testInfo: this.testInfo,
|
||||||
folderSetupFn: arg.folderSetupFn,
|
folderSetupFn: arg.folderSetupFn,
|
||||||
cleanProjectDir: arg.cleanProjectDir,
|
cleanProjectDir: arg.cleanProjectDir,
|
||||||
appSettings: arg.appSettings,
|
appSettings: arg.appSettings,
|
||||||
})
|
})
|
||||||
this.page = page
|
this.page = page
|
||||||
|
this.context = context
|
||||||
this.electronApp = electronApp
|
this.electronApp = electronApp
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
this.dir = dir
|
||||||
|
|
||||||
|
// Easier to access throughout utils
|
||||||
|
this.page.dir = dir
|
||||||
|
|
||||||
|
// Setup localStorage, addCookies, reload
|
||||||
|
await setup(this.context, this.page, this.testInfo)
|
||||||
|
|
||||||
for (const key of unsafeTypedKeys(arg.fixtures)) {
|
for (const key of unsafeTypedKeys(arg.fixtures)) {
|
||||||
const fixture = arg.fixtures[key]
|
const fixture = arg.fixtures[key]
|
||||||
@ -110,32 +117,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'
|
|
||||||
|
|||||||
@ -14,10 +14,14 @@ interface HomePageState {
|
|||||||
export class HomePageFixture {
|
export class HomePageFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
|
|
||||||
|
projectSection!: Locator
|
||||||
projectCard!: Locator
|
projectCard!: Locator
|
||||||
projectCardTitle!: Locator
|
projectCardTitle!: Locator
|
||||||
projectCardFile!: Locator
|
projectCardFile!: Locator
|
||||||
projectCardFolder!: Locator
|
projectCardFolder!: Locator
|
||||||
|
projectButtonNew!: Locator
|
||||||
|
projectButtonContinue!: Locator
|
||||||
|
projectTextName!: Locator
|
||||||
sortByDateBtn!: Locator
|
sortByDateBtn!: Locator
|
||||||
sortByNameBtn!: Locator
|
sortByNameBtn!: Locator
|
||||||
|
|
||||||
@ -28,11 +32,19 @@ export class HomePageFixture {
|
|||||||
reConstruct = (page: Page) => {
|
reConstruct = (page: Page) => {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
|
||||||
|
this.projectSection = this.page.getByTestId('home-section')
|
||||||
|
|
||||||
this.projectCard = this.page.getByTestId('project-link')
|
this.projectCard = this.page.getByTestId('project-link')
|
||||||
this.projectCardTitle = this.page.getByTestId('project-title')
|
this.projectCardTitle = this.page.getByTestId('project-title')
|
||||||
this.projectCardFile = this.page.getByTestId('project-file-count')
|
this.projectCardFile = this.page.getByTestId('project-file-count')
|
||||||
this.projectCardFolder = this.page.getByTestId('project-folder-count')
|
this.projectCardFolder = this.page.getByTestId('project-folder-count')
|
||||||
|
|
||||||
|
this.projectButtonNew = this.page.getByTestId('home-new-file')
|
||||||
|
this.projectTextName = this.page.getByTestId('cmd-bar-arg-value')
|
||||||
|
this.projectButtonContinue = this.page.getByRole('button', {
|
||||||
|
name: 'Continue',
|
||||||
|
})
|
||||||
|
|
||||||
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
||||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||||
}
|
}
|
||||||
@ -91,10 +103,25 @@ export class HomePageFixture {
|
|||||||
.toEqual(expectedState)
|
.toEqual(expectedState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createAndGoToProject = async (projectTitle: 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,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)
|
||||||
}
|
}
|
||||||
@ -187,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),
|
||||||
@ -214,7 +218,23 @@ export class SceneFixture {
|
|||||||
coords: { x: number; y: number },
|
coords: { x: number; y: number },
|
||||||
diff: number
|
diff: number
|
||||||
) => {
|
) => {
|
||||||
await expectPixelColor(this.page, colour, coords, diff)
|
let finalValue = colour
|
||||||
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
|
const pixel = (await getPixelRGBs(this.page)(coords, 1))[0]
|
||||||
|
if (!pixel) return null
|
||||||
|
finalValue = pixel
|
||||||
|
return pixel.every(
|
||||||
|
(channel, index) => Math.abs(channel - colour[index]) < diff
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.toBeTruthy()
|
||||||
|
.catch((cause) => {
|
||||||
|
throw new Error(
|
||||||
|
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
||||||
|
{ cause }
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get gizmo() {
|
get gizmo() {
|
||||||
@ -222,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,
|
||||||
@ -230,28 +251,3 @@ export class SceneFixture {
|
|||||||
await buttonToTest.click()
|
await buttonToTest.click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function expectPixelColor(
|
|
||||||
page: Page,
|
|
||||||
colour: [number, number, number],
|
|
||||||
coords: { x: number; y: number },
|
|
||||||
diff: number
|
|
||||||
) {
|
|
||||||
let finalValue = colour
|
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
|
||||||
if (!pixel) return null
|
|
||||||
finalValue = pixel
|
|
||||||
return pixel.every(
|
|
||||||
(channel, index) => Math.abs(channel - colour[index]) < diff
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.toBeTruthy()
|
|
||||||
.catch((cause) => {
|
|
||||||
throw new Error(
|
|
||||||
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
|
||||||
{ cause }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
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 {
|
||||||
|
|||||||
@ -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,86 +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'
|
||||||
import { expectPixelColor } from './fixtures/sceneFixture'
|
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
// Because onboarding relies on an app setting we need to set it as incompletel
|
||||||
if (testInfo.tags.includes('@electron')) {
|
// for all these tests.
|
||||||
return
|
|
||||||
}
|
|
||||||
await setup(context, page)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Onboarding tests', () => {
|
test.describe('Onboarding tests', () => {
|
||||||
test('Onboarding code is shown in the editor', async ({ page }) => {
|
test(
|
||||||
const u = await getUtils(page)
|
'Onboarding code is shown in the editor',
|
||||||
|
{
|
||||||
// Override beforeEach test setup
|
appSettings: {
|
||||||
await page.addInitScript(
|
app: {
|
||||||
async ({ settingsKey }) => {
|
onboardingStatus: 'incomplete',
|
||||||
// Give no initial code, so that the onboarding start is shown immediately
|
},
|
||||||
localStorage.removeItem('persistCode')
|
|
||||||
localStorage.removeItem(settingsKey)
|
|
||||||
},
|
},
|
||||||
{ settingsKey: TEST_SETTINGS_KEY }
|
cleanProjectDir: true,
|
||||||
)
|
},
|
||||||
|
async ({ context, page, homePage }) => {
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
// 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')
|
|
||||||
|
|
||||||
// Make sure the model loaded
|
|
||||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
|
||||||
const modelColor: [number, number, number] = [45, 45, 45]
|
|
||||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
|
||||||
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(8)
|
|
||||||
})
|
|
||||||
|
|
||||||
test(
|
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: 1000 }
|
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 })
|
||||||
@ -99,346 +76,362 @@ test.describe('Onboarding tests', () => {
|
|||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
'// Shelf Bracket'
|
'// Shelf Bracket'
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: jess make less shit
|
|
||||||
// Make sure the model loaded
|
|
||||||
//const XYPlanePoint = { x: 986, y: 522 } as const
|
|
||||||
//const modelColor: [number, number, number] = [76, 76, 76]
|
|
||||||
//await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
|
||||||
|
|
||||||
//await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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: 1000 })
|
|
||||||
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')
|
|
||||||
|
|
||||||
// Make sure the model loaded
|
|
||||||
const XYPlanePoint = { x: 986, y: 522 } as const
|
|
||||||
const modelColor: [number, number, number] = [76, 76, 76]
|
|
||||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
|
||||||
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('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')
|
},
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
// TODO: jess to fix
|
|
||||||
// Make sure the model loaded
|
|
||||||
//const XYPlanePoint = { x: 774, y: 516 } as const
|
|
||||||
// const modelColor: [number, number, number] = [129, 129, 129]
|
|
||||||
// await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
|
||||||
// await expectPixelColor(page, modelColor, XYPlanePoint, 20)
|
|
||||||
})
|
|
||||||
|
|
||||||
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.fixme(
|
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
|
||||||
@ -450,9 +443,8 @@ test.fixme(
|
|||||||
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' })
|
||||||
@ -471,7 +463,7 @@ test.fixme(
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Navigate into project', async () => {
|
await test.step('Navigate into project', async () => {
|
||||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
@ -487,22 +479,14 @@ test.fixme(
|
|||||||
await helpMenuButton.click()
|
await helpMenuButton.click()
|
||||||
await restartOnboardingButton.click()
|
await restartOnboardingButton.click()
|
||||||
|
|
||||||
await expect(restartConfirmationButton).toBeVisible()
|
await nextButton.hover()
|
||||||
await restartConfirmationButton.click()
|
await nextButton.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Confirm that the onboarding has restarted', async () => {
|
await test.step('Confirm that the onboarding has restarted', async () => {
|
||||||
await expect(tutorialProjectIndicator).toBeVisible()
|
await expect(tutorialProjectIndicator).toBeVisible()
|
||||||
await expect(tutorialModalText).toBeVisible()
|
await expect(tutorialModalText).toBeVisible()
|
||||||
// Make sure the model loaded
|
|
||||||
const XYPlanePoint = { x: 988, y: 523 } as const
|
|
||||||
const modelColor: [number, number, number] = [76, 76, 76]
|
|
||||||
|
|
||||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
|
||||||
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
|
||||||
await tutorialDismissButton.click()
|
await tutorialDismissButton.click()
|
||||||
// Make sure model still there.
|
|
||||||
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clear code and restart onboarding from settings', async () => {
|
await test.step('Clear code and restart onboarding from settings', async () => {
|
||||||
@ -520,11 +504,9 @@ test.fixme(
|
|||||||
|
|
||||||
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 () => {
|
||||||
@ -553,11 +598,15 @@ test(`Verify axis, origin, and horizontal snapping`, async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
test(`Verify user can double-click to edit a sketch`, async ({
|
test(`Verify user can double-click to edit a sketch`, async ({
|
||||||
app,
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
const initialCode = `closedSketch = startSketchOn('XZ')
|
const initialCode = `closedSketch = startSketchOn('XZ')
|
||||||
|> circle({ center = [8, 5], radius = 2 }, %)
|
|> circle({ center = [8, 5], radius = 2 }, %)
|
||||||
openSketch = startSketchOn('XY')
|
openSketch = startSketchOn('XY')
|
||||||
@ -566,15 +615,24 @@ openSketch = startSketchOn('XY')
|
|||||||
|> xLine(5, %)
|
|> xLine(5, %)
|
||||||
|> tangentialArcTo([10, 0], %)
|
|> tangentialArcTo([10, 0], %)
|
||||||
`
|
`
|
||||||
await app.initialise(initialCode)
|
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 = {
|
const pointInsideCircle = {
|
||||||
x: app.viewPortSize.width * 0.63,
|
x: viewPortSize.width * 0.63,
|
||||||
y: app.viewPortSize.height * 0.5,
|
y: viewPortSize.height * 0.5,
|
||||||
}
|
}
|
||||||
const pointOnPathAfterSketching = {
|
const pointOnPathAfterSketching = {
|
||||||
x: app.viewPortSize.width * 0.58,
|
x: viewPortSize.width * 0.65,
|
||||||
y: app.viewPortSize.height * 0.5,
|
y: viewPortSize.height * 0.5,
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] =
|
const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] =
|
||||||
@ -607,41 +665,59 @@ openSketch = startSketchOn('XY')
|
|||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
await exitSketch()
|
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 test.step(`Double-click on the open sketch`, async () => {
|
||||||
await moveToOpenPath()
|
await moveToOpenPath()
|
||||||
await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15)
|
await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15)
|
||||||
// There is a full execution after exiting sketch that clears the scene.
|
// There is a full execution after exiting sketch that clears the scene.
|
||||||
await app.page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await dblClickOpenPath()
|
await dblClickOpenPath()
|
||||||
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
||||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
// Wait for enter sketch mode to complete
|
// Wait for enter sketch mode to complete
|
||||||
await app.page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: [`|>xLine(5,%)`],
|
activeLines: [`|>tangentialArcTo([10,0],%)`],
|
||||||
highlightedCode: 'xLine(5,%)',
|
highlightedCode: 'tangentialArcTo([10,0],%)',
|
||||||
diagnostics: [],
|
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)
|
||||||
})
|
})
|
||||||
@ -775,7 +851,9 @@ const shellPointAndClickCapCases = [
|
|||||||
]
|
]
|
||||||
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
||||||
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
|
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
|
||||||
app,
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
@ -785,7 +863,11 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||||
extrude001 = extrude(30, sketch001)
|
extrude001 = extrude(30, sketch001)
|
||||||
`
|
`
|
||||||
await app.initialise(initialCode)
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 575, y: 200 }
|
const testPoint = { x: 575, y: 200 }
|
||||||
@ -812,7 +894,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|||||||
commandName: 'Shell',
|
commandName: 'Shell',
|
||||||
})
|
})
|
||||||
await clickOnCap()
|
await clickOnCap()
|
||||||
await app.page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
@ -828,7 +910,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|||||||
} else {
|
} else {
|
||||||
await test.step(`Preselect the cap`, async () => {
|
await test.step(`Preselect the cap`, async () => {
|
||||||
await clickOnCap()
|
await clickOnCap()
|
||||||
await app.page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
||||||
@ -860,8 +942,9 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Shell point-and-click wall', async ({
|
test('Shell point-and-click wall', async ({
|
||||||
app,
|
context,
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
@ -876,7 +959,11 @@ test('Shell point-and-click wall', async ({
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(40, sketch001)
|
extrude001 = extrude(40, sketch001)
|
||||||
`
|
`
|
||||||
await app.initialise(initialCode)
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 580, y: 180 }
|
const testPoint = { x: 580, y: 180 }
|
||||||
@ -907,7 +994,7 @@ extrude001 = extrude(40, sketch001)
|
|||||||
await clickOnCap()
|
await clickOnCap()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await clickOnWall()
|
await clickOnWall()
|
||||||
await app.page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
|||||||
@ -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] = [161, 220, 155]
|
const planeColor: [number, number, number] = [170, 220, 170]
|
||||||
const bgColor: [number, number, number] = [27, 27, 27]
|
const bgColor: [number, number, number] = [27, 27, 27]
|
||||||
const middlePixelIsColor = async (color: [number, number, number]) => {
|
const middlePixelIsColor = async (color: [number, number, number]) => {
|
||||||
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
||||||
@ -561,8 +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()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,11 @@ test.beforeEach(async ({ page }) => {
|
|||||||
|
|
||||||
test.setTimeout(60_000)
|
test.setTimeout(60_000)
|
||||||
|
|
||||||
test(
|
// We test this end to end already - getting this to work on web just to take
|
||||||
|
// a snapshot of it feels weird. I'd rather our regular tests fail.
|
||||||
|
// The primary failure is doExport now relies on the filesystem. We can follow
|
||||||
|
// up with another PR if we want this back.
|
||||||
|
test.skip(
|
||||||
'exports of each format should work',
|
'exports of each format should work',
|
||||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
||||||
async ({ page, context }) => {
|
async ({ page, context }) => {
|
||||||
@ -1164,109 +1168,3 @@ test.fixme('theme persists', async ({ page, context }) => {
|
|||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
|
||||||
test('code color goober', async ({ page, context }) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await context.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'persistCode',
|
|
||||||
`// Create a pipe using a sweep.
|
|
||||||
|
|
||||||
// Create a path for the sweep.
|
|
||||||
sweepPath = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0.05, 0.05], %)
|
|
||||||
|> line([0, 7], %)
|
|
||||||
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
|
||||||
|> line([-3, 0], %)
|
|
||||||
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
|
||||||
|> line([0, 7], %)
|
|
||||||
|
|
||||||
sweepSketch = startSketchOn('XY')
|
|
||||||
|> startProfileAt([2, 0], %)
|
|
||||||
|> arc({
|
|
||||||
angleEnd = 360,
|
|
||||||
angleStart = 0,
|
|
||||||
radius = 2
|
|
||||||
}, %)
|
|
||||||
|> sweep({
|
|
||||||
path = sweepPath,
|
|
||||||
}, %)
|
|
||||||
|> appearance({
|
|
||||||
color = "#bb00ff",
|
|
||||||
metalness = 90,
|
|
||||||
roughness = 90
|
|
||||||
}, %)
|
|
||||||
`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.clearAndCloseDebugPanel()
|
|
||||||
|
|
||||||
await expect(page, 'expect small color widget').toHaveScreenshot({
|
|
||||||
maxDiffPixels: 100,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('code color goober opening window', async ({ page, context }) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await context.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'persistCode',
|
|
||||||
`// Create a pipe using a sweep.
|
|
||||||
|
|
||||||
// Create a path for the sweep.
|
|
||||||
sweepPath = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0.05, 0.05], %)
|
|
||||||
|> line([0, 7], %)
|
|
||||||
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
|
||||||
|> line([-3, 0], %)
|
|
||||||
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
|
||||||
|> line([0, 7], %)
|
|
||||||
|
|
||||||
sweepSketch = startSketchOn('XY')
|
|
||||||
|> startProfileAt([2, 0], %)
|
|
||||||
|> arc({
|
|
||||||
angleEnd = 360,
|
|
||||||
angleStart = 0,
|
|
||||||
radius = 2
|
|
||||||
}, %)
|
|
||||||
|> sweep({
|
|
||||||
path = sweepPath,
|
|
||||||
}, %)
|
|
||||||
|> appearance({
|
|
||||||
color = "#bb00ff",
|
|
||||||
metalness = 90,
|
|
||||||
roughness = 90
|
|
||||||
}, %)
|
|
||||||
`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.clearAndCloseDebugPanel()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()
|
|
||||||
|
|
||||||
// Click the color widget
|
|
||||||
await page.locator('.cm-css-color-picker-wrapper input').click()
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page,
|
|
||||||
'expect small color widget to have window open'
|
|
||||||
).toHaveScreenshot({
|
|
||||||
maxDiffPixels: 100,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 39 KiB |
@ -14,7 +14,7 @@ export const TEST_SETTINGS = {
|
|||||||
},
|
},
|
||||||
modeling: {
|
modeling: {
|
||||||
defaultUnit: 'in',
|
defaultUnit: 'in',
|
||||||
mouseControls: 'Zoo',
|
mouseControls: 'KittyCAD',
|
||||||
cameraProjection: 'perspective',
|
cameraProjection: 'perspective',
|
||||||
showDebugPanel: true,
|
showDebugPanel: true,
|
||||||
},
|
},
|
||||||
@ -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(
|
||||||
|
|||||||
@ -1,22 +1,20 @@
|
|||||||
import {
|
import {
|
||||||
expect,
|
expect,
|
||||||
Page,
|
|
||||||
Download,
|
|
||||||
BrowserContext,
|
BrowserContext,
|
||||||
TestInfo,
|
TestInfo,
|
||||||
_electron as electron,
|
_electron as electron,
|
||||||
Locator,
|
Locator,
|
||||||
test,
|
|
||||||
} from '@playwright/test'
|
} from '@playwright/test'
|
||||||
|
import { test, Page } from './zoo-test'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import fsSync from 'fs'
|
import fsSync from 'fs'
|
||||||
import { join } from 'path'
|
import path from 'path'
|
||||||
import pixelMatch from 'pixelmatch'
|
import pixelMatch from 'pixelmatch'
|
||||||
import { PNG } from 'pngjs'
|
import { PNG } from 'pngjs'
|
||||||
import { Protocol } from 'playwright-core/types/protocol'
|
import { Protocol } from 'playwright-core/types/protocol'
|
||||||
import type { Models } from '@kittycad/lib'
|
import type { Models } from '@kittycad/lib'
|
||||||
import { APP_NAME, COOKIE_NAME } from 'lib/constants'
|
import { COOKIE_NAME } from 'lib/constants'
|
||||||
import { secrets } from './secrets'
|
import { secrets } from './secrets'
|
||||||
import {
|
import {
|
||||||
TEST_SETTINGS_KEY,
|
TEST_SETTINGS_KEY,
|
||||||
@ -30,6 +28,134 @@ import { isErrorWhitelisted } from './lib/console-error-whitelist'
|
|||||||
import { isArray } from 'lib/utils'
|
import { isArray } from 'lib/utils'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
|
|
||||||
|
// The below is copied from playwright-core because it exports none of them :(
|
||||||
|
import { Env, BrowserContextOptions } from 'playwright-core'
|
||||||
|
import type * as channels from '@protocol/channels'
|
||||||
|
|
||||||
|
// Copied from playwright-core
|
||||||
|
function envObjectToArray(env: Env): { name: string; value: string }[] {
|
||||||
|
const result: { name: string; value: string }[] = []
|
||||||
|
for (const name in env) {
|
||||||
|
if (!Object.is(env[name], undefined))
|
||||||
|
result.push({ name, value: String(env[name]) })
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from playwright-core
|
||||||
|
export async function toClientCertificatesProtocol(
|
||||||
|
certs?: BrowserContextOptions['clientCertificates']
|
||||||
|
): Promise<channels.PlaywrightNewRequestParams['clientCertificates']> {
|
||||||
|
if (!certs) return undefined
|
||||||
|
|
||||||
|
const bufferizeContent = async (
|
||||||
|
value?: Buffer,
|
||||||
|
path?: string
|
||||||
|
): Promise<Buffer | undefined> => {
|
||||||
|
if (value) return value
|
||||||
|
if (path) return await fs.promises.readFile(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
certs.map(async (cert) => ({
|
||||||
|
origin: cert.origin,
|
||||||
|
cert: await bufferizeContent(cert.cert, cert.certPath),
|
||||||
|
key: await bufferizeContent(cert.key, cert.keyPath),
|
||||||
|
pfx: await bufferizeContent(cert.pfx, cert.pfxPath),
|
||||||
|
passphrase: cert.passphrase,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from playwright-core
|
||||||
|
function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
|
||||||
|
if (acceptDownloads === undefined) return undefined
|
||||||
|
if (acceptDownloads) return 'accept'
|
||||||
|
return 'deny'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from playwright-core
|
||||||
|
function prepareRecordHarOptions(
|
||||||
|
options: BrowserContextOptions['recordHar']
|
||||||
|
): channels.RecordHarOptions | undefined {
|
||||||
|
if (!options) return
|
||||||
|
return {
|
||||||
|
path: options.path,
|
||||||
|
content: options.content || (options.omitContent ? 'omit' : undefined),
|
||||||
|
urlGlob: isString(options.urlFilter) ? options.urlFilter : undefined,
|
||||||
|
urlRegexSource: isRegExp(options.urlFilter)
|
||||||
|
? options.urlFilter.source
|
||||||
|
: undefined,
|
||||||
|
urlRegexFlags: isRegExp(options.urlFilter)
|
||||||
|
? options.urlFilter.flags
|
||||||
|
: undefined,
|
||||||
|
mode: options.mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from playwright-core
|
||||||
|
async function prepareStorageState(
|
||||||
|
options: BrowserContextOptions
|
||||||
|
): Promise<channels.BrowserNewContextParams['storageState']> {
|
||||||
|
if (typeof options.storageState !== 'string') return options.storageState
|
||||||
|
try {
|
||||||
|
return JSON.parse(await fs.promises.readFile(options.storageState, 'utf8'))
|
||||||
|
} catch (e) {
|
||||||
|
rewriteErrorMessage(
|
||||||
|
e,
|
||||||
|
`Error reading storage state from ${options.storageState}:\n` + e.message
|
||||||
|
)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from playwright-core
|
||||||
|
async function prepareBrowserContextParams(
|
||||||
|
options: BrowserContextOptions
|
||||||
|
): Promise<channels.BrowserNewContextParams> {
|
||||||
|
if (options.videoSize && !options.videosPath)
|
||||||
|
throw new Error(`"videoSize" option requires "videosPath" to be specified`)
|
||||||
|
if (options.extraHTTPHeaders)
|
||||||
|
network.validateHeaders(options.extraHTTPHeaders)
|
||||||
|
const contextParams: channels.BrowserNewContextParams = {
|
||||||
|
...options,
|
||||||
|
viewport: options.viewport === null ? undefined : options.viewport,
|
||||||
|
noDefaultViewport: options.viewport === null,
|
||||||
|
extraHTTPHeaders: options.extraHTTPHeaders
|
||||||
|
? headersObjectToArray(options.extraHTTPHeaders)
|
||||||
|
: undefined,
|
||||||
|
storageState: await prepareStorageState(options),
|
||||||
|
serviceWorkers: options.serviceWorkers,
|
||||||
|
recordHar: prepareRecordHarOptions(options.recordHar),
|
||||||
|
colorScheme:
|
||||||
|
options.colorScheme === null ? 'no-override' : options.colorScheme,
|
||||||
|
reducedMotion:
|
||||||
|
options.reducedMotion === null ? 'no-override' : options.reducedMotion,
|
||||||
|
forcedColors:
|
||||||
|
options.forcedColors === null ? 'no-override' : options.forcedColors,
|
||||||
|
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
|
||||||
|
clientCertificates: await toClientCertificatesProtocol(
|
||||||
|
options.clientCertificates
|
||||||
|
),
|
||||||
|
}
|
||||||
|
if (!contextParams.recordVideo && options.videosPath) {
|
||||||
|
contextParams.recordVideo = {
|
||||||
|
dir: options.videosPath,
|
||||||
|
size: options.videoSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (contextParams.recordVideo && contextParams.recordVideo.dir)
|
||||||
|
contextParams.recordVideo.dir = path.resolve(
|
||||||
|
process.cwd(),
|
||||||
|
contextParams.recordVideo.dir
|
||||||
|
)
|
||||||
|
return contextParams
|
||||||
|
}
|
||||||
|
|
||||||
|
const toNormalizedCode = (text: string) => {
|
||||||
|
return text.replace(/\s+/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
type TestColor = [number, number, number]
|
type TestColor = [number, number, number]
|
||||||
export const TEST_COLORS = {
|
export const TEST_COLORS = {
|
||||||
WHITE: [249, 249, 249] as TestColor,
|
WHITE: [249, 249, 249] as TestColor,
|
||||||
@ -98,11 +224,16 @@ async function removeCurrentCode(page: Page) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function sendCustomCmd(page: Page, cmd: EngineCommand) {
|
export async function sendCustomCmd(page: Page, cmd: EngineCommand) {
|
||||||
await page.getByTestId('custom-cmd-input').fill(JSON.stringify(cmd))
|
const json = JSON.stringify(cmd)
|
||||||
|
await page.getByTestId('custom-cmd-input').fill(json)
|
||||||
|
await expect(page.getByTestId('custom-cmd-input')).toHaveValue(json)
|
||||||
|
await page.getByTestId('custom-cmd-send-button').scrollIntoViewIfNeeded()
|
||||||
await page.getByTestId('custom-cmd-send-button').click()
|
await page.getByTestId('custom-cmd-send-button').click()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearCommandLogs(page: Page) {
|
async function clearCommandLogs(page: Page) {
|
||||||
|
await page.getByTestId('custom-cmd-input').fill('')
|
||||||
|
await page.getByTestId('clear-commands').scrollIntoViewIfNeeded()
|
||||||
await page.getByTestId('clear-commands').click()
|
await page.getByTestId('clear-commands').click()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +281,19 @@ export async function closePane(page: Page, testId: string) {
|
|||||||
|
|
||||||
async function openKclCodePanel(page: Page) {
|
async function openKclCodePanel(page: Page) {
|
||||||
await openPane(page, 'code-pane-button')
|
await openPane(page, 'code-pane-button')
|
||||||
|
|
||||||
|
// Code Mirror lazy loads text! Wowza! Let's force-load the text for tests.
|
||||||
|
await page.evaluate(() => {
|
||||||
|
// editorManager is available on the window object.
|
||||||
|
//@ts-ignore this is in an entirely different context that tsc can't see.
|
||||||
|
editorManager._editorView.dispatch({
|
||||||
|
selection: {
|
||||||
|
//@ts-ignore this is in an entirely different context that tsc can't see.
|
||||||
|
anchor: editorManager._editorView.docView.length,
|
||||||
|
},
|
||||||
|
scrollIntoView: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeKclCodePanel(page: Page) {
|
async function closeKclCodePanel(page: Page) {
|
||||||
@ -165,6 +309,9 @@ async function closeKclCodePanel(page: Page) {
|
|||||||
|
|
||||||
async function openDebugPanel(page: Page) {
|
async function openDebugPanel(page: Page) {
|
||||||
await openPane(page, 'debug-pane-button')
|
await openPane(page, 'debug-pane-button')
|
||||||
|
|
||||||
|
// The debug pane needs time to load everything.
|
||||||
|
await page.waitForTimeout(3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function closeDebugPanel(page: Page) {
|
export async function closeDebugPanel(page: Page) {
|
||||||
@ -412,6 +559,10 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
.boundingBox({ timeout: 5_000 })
|
.boundingBox({ timeout: 5_000 })
|
||||||
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
|
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
|
||||||
codeLocator: page.locator('.cm-content'),
|
codeLocator: page.locator('.cm-content'),
|
||||||
|
crushKclCodeIntoOneLineAndThenMaybeSome: async () => {
|
||||||
|
const code = await page.locator('.cm-content').innerText()
|
||||||
|
return code.replaceAll(' ', '').replaceAll('\n', '')
|
||||||
|
},
|
||||||
normalisedEditorCode: async () => {
|
normalisedEditorCode: async () => {
|
||||||
const code = await page.locator('.cm-content').innerText()
|
const code = await page.locator('.cm-content').innerText()
|
||||||
return normaliseKclNumbers(code)
|
return normaliseKclNumbers(code)
|
||||||
@ -482,13 +633,18 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
toNormalizedCode: (text: string) => {
|
toNormalizedCode(text: string) {
|
||||||
return text.replace(/\s+/g, '')
|
return toNormalizedCode(text)
|
||||||
},
|
},
|
||||||
|
|
||||||
editorTextMatches: async (code: string) => {
|
async editorTextMatches(code: string) {
|
||||||
const editor = page.locator(editorSelector)
|
const editor = page.locator(editorSelector)
|
||||||
return expect(editor).toHaveText(code, { useInnerText: true })
|
return expect
|
||||||
|
.poll(async () => {
|
||||||
|
const text = await editor.textContent()
|
||||||
|
return toNormalizedCode(text ?? '')
|
||||||
|
})
|
||||||
|
.toContain(toNormalizedCode(code))
|
||||||
},
|
},
|
||||||
|
|
||||||
pasteCodeInEditor: async (code: string) => {
|
pasteCodeInEditor: async (code: string) => {
|
||||||
@ -514,7 +670,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
await page.getByTestId('create-file-button').click()
|
await page.getByTestId('create-file-button').click()
|
||||||
await page.getByTestId('file-rename-field').fill(name)
|
await page.getByTestId('tree-input-field').fill(name)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -674,6 +830,34 @@ export const makeTemplate: (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PLAYWRIGHT_DOWNLOAD_DIR = 'downloads-during-playwright'
|
||||||
|
|
||||||
|
export const getPlaywrightDownloadDir = (page: Page) => {
|
||||||
|
return path.resolve(page.dir, PLAYWRIGHT_DOWNLOAD_DIR)
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveDownloadedFileTo = async (page: Page, toLocation: string) => {
|
||||||
|
await fsp.mkdir(path.dirname(toLocation), { recursive: true })
|
||||||
|
|
||||||
|
const downloadDir = getPlaywrightDownloadDir(page)
|
||||||
|
|
||||||
|
// Expect there to be at least one file
|
||||||
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
|
const files = await fsp.readdir(downloadDir)
|
||||||
|
return files.length
|
||||||
|
})
|
||||||
|
.toBeGreaterThan(0)
|
||||||
|
|
||||||
|
// Go through the downloads dir and move files to new location
|
||||||
|
const files = await fsp.readdir(downloadDir)
|
||||||
|
|
||||||
|
// Assumption: only ever one file here.
|
||||||
|
for (let file of files) {
|
||||||
|
await fsp.rename(path.resolve(downloadDir, file), toLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface Paths {
|
export interface Paths {
|
||||||
modelPath: string
|
modelPath: string
|
||||||
imagePath: string
|
imagePath: string
|
||||||
@ -686,7 +870,8 @@ export const doExport = async (
|
|||||||
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
||||||
): Promise<Paths> => {
|
): Promise<Paths> => {
|
||||||
if (exportFrom === 'dropdown') {
|
if (exportFrom === 'dropdown') {
|
||||||
await page.getByRole('button', { name: APP_NAME }).click()
|
await page.getByTestId('project-sidebar-toggle').click()
|
||||||
|
|
||||||
const exportMenuButton = page.getByRole('button', {
|
const exportMenuButton = page.getByRole('button', {
|
||||||
name: 'Export current part',
|
name: 'Export current part',
|
||||||
})
|
})
|
||||||
@ -727,25 +912,12 @@ export const doExport = async (
|
|||||||
}
|
}
|
||||||
await expect(page.getByText('Confirm Export')).toBeVisible()
|
await expect(page.getByText('Confirm Export')).toBeVisible()
|
||||||
|
|
||||||
const getPromiseAndResolve = () => {
|
|
||||||
let resolve: any = () => {}
|
|
||||||
const promise = new Promise<Download>((r) => {
|
|
||||||
resolve = r
|
|
||||||
})
|
|
||||||
return [promise, resolve]
|
|
||||||
}
|
|
||||||
|
|
||||||
const [downloadPromise1, downloadResolve1] = getPromiseAndResolve()
|
|
||||||
let downloadCnt = 0
|
|
||||||
|
|
||||||
if (exportFrom === 'dropdown')
|
|
||||||
page.on('download', async (download) => {
|
|
||||||
if (downloadCnt === 0) {
|
|
||||||
downloadResolve1(download)
|
|
||||||
}
|
|
||||||
downloadCnt++
|
|
||||||
})
|
|
||||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||||
|
|
||||||
|
// This usually happens immediately after. If we're too slow we don't
|
||||||
|
// catch it.
|
||||||
|
await expect(page.getByText('Exported successfully')).toBeVisible()
|
||||||
|
|
||||||
if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') {
|
if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') {
|
||||||
return {
|
return {
|
||||||
modelPath: '',
|
modelPath: '',
|
||||||
@ -755,15 +927,12 @@ export const doExport = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle download
|
// Handle download
|
||||||
const download = await downloadPromise1
|
|
||||||
const downloadLocationer = (extra = '', isImage = false) =>
|
const downloadLocationer = (extra = '', isImage = false) =>
|
||||||
`./e2e/playwright/export-snapshots/${output.type}-${
|
`./e2e/playwright/export-snapshots/${output.type}-${
|
||||||
'storage' in output ? output.storage : ''
|
'storage' in output ? output.storage : ''
|
||||||
}${extra}.${isImage ? 'png' : output.type}`
|
}${extra}.${isImage ? 'png' : output.type}`
|
||||||
const downloadLocation = downloadLocationer()
|
const downloadLocation = downloadLocationer()
|
||||||
|
|
||||||
await download.saveAs(downloadLocation)
|
|
||||||
|
|
||||||
if (output.type === 'step') {
|
if (output.type === 'step') {
|
||||||
// stable timestamps for step files
|
// stable timestamps for step files
|
||||||
const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
|
const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
|
||||||
@ -772,6 +941,12 @@ export const doExport = async (
|
|||||||
'1970-01-01T00:00:00.0+00:00'
|
'1970-01-01T00:00:00.0+00:00'
|
||||||
)
|
)
|
||||||
await fsp.writeFile(downloadLocation, newFileContents)
|
await fsp.writeFile(downloadLocation, newFileContents)
|
||||||
|
} else {
|
||||||
|
// By default all files are downloaded to the same place in playwright
|
||||||
|
// (declared in src/lib/exportSave)
|
||||||
|
// To remain consistent with our old web tests, we want to move some downloads
|
||||||
|
// (images) to another directory.
|
||||||
|
await moveDownloadedFileTo(page, downloadLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -798,6 +973,8 @@ export async function tearDown(page: Page, testInfo: TestInfo) {
|
|||||||
// It seems it's best to give the browser about 3s to close things
|
// It seems it's best to give the browser about 3s to close things
|
||||||
// It's not super reliable but we have no real other choice for now
|
// It's not super reliable but we have no real other choice for now
|
||||||
await page.waitForTimeout(3000)
|
await page.waitForTimeout(3000)
|
||||||
|
|
||||||
|
await testInfo.tronApp?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// settingsOverrides may need to be augmented to take more generic items,
|
// settingsOverrides may need to be augmented to take more generic items,
|
||||||
@ -808,12 +985,24 @@ export async function setup(
|
|||||||
testInfo?: TestInfo
|
testInfo?: TestInfo
|
||||||
) {
|
) {
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY }) => {
|
async ({
|
||||||
|
token,
|
||||||
|
settingsKey,
|
||||||
|
settings,
|
||||||
|
IS_PLAYWRIGHT_KEY,
|
||||||
|
PLAYWRIGHT_TEST_DIR,
|
||||||
|
PERSIST_MODELING_CONTEXT,
|
||||||
|
}) => {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||||
localStorage.setItem('persistCode', ``)
|
localStorage.setItem('persistCode', ``)
|
||||||
|
localStorage.setItem(
|
||||||
|
PERSIST_MODELING_CONTEXT,
|
||||||
|
JSON.stringify({ openPanes: ['code'] })
|
||||||
|
)
|
||||||
localStorage.setItem(settingsKey, settings)
|
localStorage.setItem(settingsKey, settings)
|
||||||
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
|
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
|
||||||
|
localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
token: secrets.token,
|
token: secrets.token,
|
||||||
@ -830,6 +1019,8 @@ export async function setup(
|
|||||||
} as Partial<SaveSettingsPayload>,
|
} as Partial<SaveSettingsPayload>,
|
||||||
}),
|
}),
|
||||||
IS_PLAYWRIGHT_KEY,
|
IS_PLAYWRIGHT_KEY,
|
||||||
|
PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app.projectDirectory,
|
||||||
|
PERSIST_MODELING_CONTEXT,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -848,12 +1039,15 @@ export async function setup(
|
|||||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||||
|
|
||||||
// Trigger a navigation, since loading file:// doesn't.
|
// Trigger a navigation, since loading file:// doesn't.
|
||||||
await page.reload()
|
// await page.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let electronApp = undefined
|
||||||
|
let context = undefined
|
||||||
|
let page = undefined
|
||||||
|
|
||||||
export async function setupElectron({
|
export async function setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn,
|
|
||||||
cleanProjectDir = true,
|
cleanProjectDir = true,
|
||||||
appSettings,
|
appSettings,
|
||||||
}: {
|
}: {
|
||||||
@ -876,7 +1070,7 @@ export async function setupElectron({
|
|||||||
await fsp.mkdir(projectDirName)
|
await fsp.mkdir(projectDirName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const electronApp = await electron.launch({
|
const options = {
|
||||||
args: ['.', '--no-sandbox'],
|
args: ['.', '--no-sandbox'],
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
@ -886,14 +1080,22 @@ export async function setupElectron({
|
|||||||
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
|
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
|
||||||
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
|
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
|
||||||
: {}),
|
: {}),
|
||||||
})
|
}
|
||||||
const context = electronApp.context()
|
|
||||||
const page = await electronApp.firstWindow()
|
// Do this once and then reuse window on subsequent calls.
|
||||||
context.on('console', console.log)
|
if (!electronApp) {
|
||||||
page.on('console', console.log)
|
electronApp = await electron.launch(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context || !page) {
|
||||||
|
context = electronApp.context()
|
||||||
|
page = await electronApp.firstWindow()
|
||||||
|
context.on('console', console.log)
|
||||||
|
page.on('console', console.log)
|
||||||
|
}
|
||||||
|
|
||||||
if (cleanProjectDir) {
|
if (cleanProjectDir) {
|
||||||
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
|
const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
|
||||||
const settingsOverrides = TOML.stringify(
|
const settingsOverrides = TOML.stringify(
|
||||||
appSettings
|
appSettings
|
||||||
? {
|
? {
|
||||||
@ -920,11 +1122,7 @@ export async function setupElectron({
|
|||||||
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
|
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
|
||||||
}
|
}
|
||||||
|
|
||||||
await folderSetupFn?.(projectDirName)
|
return { electronApp, page, context, dir: projectDirName, options }
|
||||||
|
|
||||||
await setup(context, page)
|
|
||||||
|
|
||||||
return { electronApp, page, dir: projectDirName }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
||||||
@ -1010,7 +1208,7 @@ export async function createProject({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function executorInputPath(fileName: string): string {
|
export function executorInputPath(fileName: string): string {
|
||||||
return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
|
return path.join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doAndWaitForImageDiff(
|
export async function doAndWaitForImageDiff(
|
||||||
@ -1101,3 +1299,12 @@ export function getPixelRGBs(page: Page) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function pollEditorLinesSelectedLength(page: Page, lines: number) {
|
||||||
|
return expect
|
||||||
|
.poll(async () => {
|
||||||
|
const lines = await page.locator('.cm-activeLine').all()
|
||||||
|
return lines.length
|
||||||
|
})
|
||||||
|
.toBe(lines)
|
||||||
|
}
|
||||||
|
|||||||
@ -1,23 +1,14 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
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('Testing Camera Movement', () => {
|
test.describe('Testing Camera Movement', () => {
|
||||||
test('Can move camera reliably', async ({ page, context }) => {
|
test('Can move camera reliably', async ({ page, context, homePage }) => {
|
||||||
test.skip(process.platform === 'darwin', 'Can move camera reliably')
|
|
||||||
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.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
|
|
||||||
@ -183,6 +174,7 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
|
|
||||||
test('Zoom should be consistent when exiting or entering sketches', async ({
|
test('Zoom should be consistent when exiting or entering sketches', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
||||||
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
||||||
@ -190,9 +182,9 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
|
|
||||||
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
||||||
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 expect(
|
await expect(
|
||||||
@ -344,7 +336,10 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`Zoom by scroll should not fire while orbiting`, async ({ page }) => {
|
test(`Zoom by scroll should not fire while orbiting`, async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
}) => {
|
||||||
/**
|
/**
|
||||||
* Currently we only allow zooming by scroll when no other camera movement is happening,
|
* Currently we only allow zooming by scroll when no other camera movement is happening,
|
||||||
* set within cameraMouseDragGuards in cameraControls.ts,
|
* set within cameraMouseDragGuards in cameraControls.ts,
|
||||||
@ -383,7 +378,7 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
const expectedOrbitCamZPosition = 64.0
|
const expectedOrbitCamZPosition = 64.0
|
||||||
|
|
||||||
await test.step(`Test setup`, async () => {
|
await test.step(`Test setup`, async () => {
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
// This test requires the mouse controls to be set to Solidworks
|
// This test requires the mouse controls to be set to Solidworks
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -479,26 +474,4 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Right-click opens context menu when not dragged', async ({ page }) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await test.step(`The menu should not show if we drag the mouse`, async () => {
|
|
||||||
await page.mouse.move(900, 200)
|
|
||||||
await page.mouse.down({ button: 'right' })
|
|
||||||
await page.mouse.move(900, 300)
|
|
||||||
await page.mouse.up({ button: 'right' })
|
|
||||||
|
|
||||||
await expect(page.getByTestId('view-controls-menu')).not.toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`The menu should show if we don't drag the mouse`, async () => {
|
|
||||||
await page.mouse.move(900, 200)
|
|
||||||
await page.mouse.down({ button: 'right' })
|
|
||||||
await page.mouse.up({ button: 'right' })
|
|
||||||
|
|
||||||
await expect(page.getByTestId('view-controls-menu')).toBeVisible()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,71 +1,60 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { getUtils, setup, tearDown, TEST_COLORS } from './test-utils'
|
import {
|
||||||
|
getUtils,
|
||||||
|
TEST_COLORS,
|
||||||
|
pollEditorLinesSelectedLength,
|
||||||
|
} from './test-utils'
|
||||||
import { XOR } from 'lib/utils'
|
import { XOR } 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('Testing constraints', () => {
|
test.describe('Testing constraints', () => {
|
||||||
test('Can constrain line length', async ({ page }) => {
|
test('Can constrain line length', async ({ 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, %)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
// constants and locators
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
const lengthValue = {
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
old: '20',
|
|
||||||
new: '25',
|
|
||||||
}
|
|
||||||
const cmdBarKclInput = page
|
|
||||||
.getByTestId('cmd-bar-arg-value')
|
|
||||||
.getByRole('textbox')
|
|
||||||
const cmdBarSubmitButton = page.getByRole('button', {
|
|
||||||
name: 'arrow right Continue',
|
|
||||||
})
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
// Click the line of code for line.
|
// Click the line of code for line.
|
||||||
// TODO remove this and reinstate `await topHorzSegmentClick()`
|
await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||||
await page.getByText(`line([0, ${lengthValue.old}], %)`).click()
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// enter sketch again
|
// enter sketch again
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(500) // wait for animation
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
const startXPx = 500
|
||||||
|
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.click(834, 244)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'dimension Length', exact: true })
|
.getByRole('button', { name: 'dimension Length', exact: true })
|
||||||
.click()
|
.click()
|
||||||
await expect(cmdBarKclInput).toHaveText('20')
|
await page.getByText('Add constraining value').click()
|
||||||
await cmdBarKclInput.fill(lengthValue.new)
|
|
||||||
await expect(
|
|
||||||
page.getByText(`Can't calculate`),
|
|
||||||
`Something went wrong with the KCL expression evaluation`
|
|
||||||
).not.toBeVisible()
|
|
||||||
await cmdBarSubmitButton.click()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`length001 = ${lengthValue.new}sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
`length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Make sure we didn't pop out of sketch mode.
|
// Make sure we didn't pop out of sketch mode.
|
||||||
@ -73,41 +62,48 @@ test.describe('Testing constraints', () => {
|
|||||||
page.getByRole('button', { name: 'Exit Sketch' })
|
page.getByRole('button', { name: 'Exit Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
await page.waitForTimeout(500) // wait for animation
|
await page.waitForTimeout(2500) // wait for animation
|
||||||
|
|
||||||
// Exit sketch
|
// Exit sketch
|
||||||
await page.keyboard.press('Escape')
|
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||||
await expect(
|
await expect
|
||||||
page.getByRole('button', { name: 'Exit Sketch' })
|
.poll(async () => {
|
||||||
).not.toBeVisible()
|
await page.keyboard.press('Escape', { delay: 500 })
|
||||||
|
return page.getByRole('button', { name: 'Exit Sketch' }).isVisible()
|
||||||
|
})
|
||||||
|
.toBe(true)
|
||||||
})
|
})
|
||||||
test(`Remove constraints`, async ({ page }) => {
|
test(`Remove constraints`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 79
|
`yo = 79
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> line([74.36, 130.4], %, $seg01)
|
|> line([74.36, 130.4], %, $seg01)
|
||||||
|> line([78.92, -120.11], %)
|
|> line([78.92, -120.11], %)
|
||||||
|> angledLine([segAng(seg01), yo], %)
|
|> angledLine([segAng(seg01), yo], %)
|
||||||
|> line([41.19, 58.97 + 5], %)
|
|> line([41.19, 58.97 + 5], %)
|
||||||
part002 = startSketchOn('XZ')
|
part002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([299.05, 120], %)
|
|> startProfileAt([299.05, 120], %)
|
||||||
|> xLine(-385.34, %, $seg_what)
|
|> xLine(-385.34, %, $seg_what)
|
||||||
|> yLine(-170.06, %)
|
|> yLine(-170.06, %)
|
||||||
|> xLine(segLen(seg_what), %)
|
|> xLine(segLen(seg_what), %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const line3 = await u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`)
|
const line3 = await u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`)
|
||||||
|
|
||||||
await page.mouse.click(line3.x, line3.y)
|
await page.mouse.click(line3.x, line3.y)
|
||||||
@ -120,8 +116,8 @@ part002 = startSketchOn('XZ')
|
|||||||
await page.getByRole('button', { name: 'remove constraints' }).click()
|
await page.getByRole('button', { name: 'remove constraints' }).click()
|
||||||
|
|
||||||
await page.getByText('line([39.13, 68.63], %)').click()
|
await page.getByText('line([39.13, 68.63], %)').click()
|
||||||
|
await pollEditorLinesSelectedLength(page, 1)
|
||||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
await expect(activeLinesContent).toHaveLength(1)
|
|
||||||
await expect(activeLinesContent[0]).toHaveText('|> line([39.13, 68.63], %)')
|
await expect(activeLinesContent[0]).toHaveText('|> line([39.13, 68.63], %)')
|
||||||
|
|
||||||
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
@ -139,43 +135,75 @@ part002 = startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, offset } of cases) {
|
for (const { testName, offset } of cases) {
|
||||||
test(`${testName}`, async ({ page }) => {
|
test(`${testName}`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 5
|
`yo = 5
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> line([74.36, 130.4], %, $seg01)
|
|> line([74.36, 130.4], %, $seg01)
|
||||||
|> line([78.92, -120.11], %)
|
|> line([78.92, -120.11], %)
|
||||||
|> angledLine([segAng(seg01), 78.33], %)
|
|> angledLine([segAng(seg01), 78.33], %)
|
||||||
|> line([51.19, 48.97], %)
|
|> line([51.19, 48.97], %)
|
||||||
part002 = startSketchOn('XZ')
|
part002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([299.05, 231.45], %)
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|> xLine(-425.34, %, $seg_what)
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|> yLine(-264.06, %)
|
|> yLine(-264.06, %)
|
||||||
|> xLine(segLen(seg_what), %)
|
|> xLine(segLen(seg_what), %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isChecked = await createNewVariableCheckbox.isChecked()
|
||||||
|
const addVariable = testName === 'Add variable'
|
||||||
|
XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct
|
||||||
|
(await createNewVariableCheckbox.click())
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Add constraining value' })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// Wait for the codemod to take effect
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(`angle: -57,`)
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
`offset: ${offset},`
|
||||||
|
)
|
||||||
|
|
||||||
|
await pollEditorLinesSelectedLength(page, 2)
|
||||||
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
|
await expect(activeLinesContent[0]).toHaveText(
|
||||||
|
`|> line([74.36, 130.4], %, $seg01)`
|
||||||
|
)
|
||||||
|
await expect(activeLinesContent[1]).toHaveText(`}, %)`)
|
||||||
|
|
||||||
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
})
|
})
|
||||||
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.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
await page.getByText('line([74.36, 130.4], %, $seg01)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Give time for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const [line1, line3] = await Promise.all([
|
const [line1, line3] = await Promise.all([
|
||||||
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||||
])
|
])
|
||||||
|
|
||||||
await page.mouse.click(line1.x, line1.y)
|
await page.mouse.click(line1.x, line1.y)
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await page.mouse.click(line3.x, line3.y)
|
|
||||||
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.click(line3.x, line3.y)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page
|
await page
|
||||||
.getByRole('button', {
|
.getByRole('button', {
|
||||||
name: 'Length: open menu',
|
name: 'Length: open menu',
|
||||||
@ -203,6 +231,7 @@ part002 = startSketchOn('XZ')
|
|||||||
`offset = ${offset},`
|
`offset = ${offset},`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await pollEditorLinesSelectedLength(page, 2)
|
||||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
await expect(activeLinesContent[0]).toHaveText(
|
await expect(activeLinesContent[0]).toHaveText(
|
||||||
`|> line([74.36, 130.4], %, $seg01)`
|
`|> line([74.36, 130.4], %, $seg01)`
|
||||||
@ -238,33 +267,37 @@ part002 = startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, value, constraint } of cases) {
|
for (const { testName, value, constraint } of cases) {
|
||||||
test(`${constraint} - ${testName}`, async ({ page }) => {
|
test(`${constraint} - ${testName}`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 5
|
`yo = 5
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> line([74.36, 130.4], %)
|
|> line([74.36, 130.4], %)
|
||||||
|> line([78.92, -120.11], %)
|
|> line([78.92, -120.11], %)
|
||||||
|> line([9.16, 77.79], %)
|
|> line([9.16, 77.79], %)
|
||||||
|> line([51.19, 48.97], %)
|
|> line([51.19, 48.97], %)
|
||||||
part002 = startSketchOn('XZ')
|
part002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([299.05, 231.45], %)
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|> xLine(-425.34, %, $seg_what)
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|> yLine(-264.06, %)
|
|> yLine(-264.06, %)
|
||||||
|> xLine(segLen(seg_what), %)
|
|> xLine(segLen(seg_what), %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const [line1, line3] = await Promise.all([
|
const [line1, line3] = await Promise.all([
|
||||||
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||||
@ -344,33 +377,37 @@ part002 = startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, constraint } of cases) {
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
test(`${constraint} - ${testName}`, async ({ page }) => {
|
test(`${constraint} - ${testName}`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 5
|
`yo = 5
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> line([74.36, 130.4], %)
|
|> line([74.36, 130.4], %)
|
||||||
|> line([78.92, -120.11], %)
|
|> line([78.92, -120.11], %)
|
||||||
|> line([9.16, 77.79], %)
|
|> line([9.16, 77.79], %)
|
||||||
|> line([51.19, 48.97], %)
|
|> line([51.19, 48.97], %)
|
||||||
part002 = startSketchOn('XZ')
|
part002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([299.05, 231.45], %)
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|> xLine(-425.34, %, $seg_what)
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|> yLine(-264.06, %)
|
|> yLine(-264.06, %)
|
||||||
|> xLine(segLen(seg_what), %)
|
|> xLine(segLen(seg_what), %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
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.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const [line3] = await Promise.all([
|
const [line3] = await Promise.all([
|
||||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||||
])
|
])
|
||||||
@ -381,9 +418,11 @@ part002 = startSketchOn('XZ')
|
|||||||
await page.mouse.click(900, 250)
|
await page.mouse.click(900, 250)
|
||||||
}
|
}
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(line3.x, line3.y)
|
await page.mouse.click(line3.x, line3.y)
|
||||||
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page
|
await page
|
||||||
.getByRole('button', {
|
.getByRole('button', {
|
||||||
name: 'Length: open menu',
|
name: 'Length: open menu',
|
||||||
@ -451,33 +490,37 @@ part002 = startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, axisSelect } of cases) {
|
for (const { testName, addVariable, value, axisSelect } of cases) {
|
||||||
test(`${testName}`, async ({ page }) => {
|
test(`${testName}`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 5
|
`yo = 5
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> line([74.36, 130.4], %)
|
|> line([74.36, 130.4], %)
|
||||||
|> line([78.92, -120.11], %)
|
|> line([78.92, -120.11], %)
|
||||||
|> line([9.16, 77.79], %)
|
|> line([9.16, 77.79], %)
|
||||||
|> line([51.19, 48.97], %)
|
|> line([51.19, 48.97], %)
|
||||||
part002 = startSketchOn('XZ')
|
part002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([299.05, 231.45], %)
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|> xLine(-425.34, %, $seg_what)
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|> yLine(-264.06, %)
|
|> yLine(-264.06, %)
|
||||||
|> xLine(segLen(seg_what), %)
|
|> xLine(segLen(seg_what), %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
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.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const [line1, line3] = await Promise.all([
|
const [line1, line3] = await Promise.all([
|
||||||
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||||
@ -549,33 +592,37 @@ part002 = startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, constraint } of cases) {
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
test(`${testName}`, async ({ page }) => {
|
test(`${testName}`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 5
|
`yo = 5
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> line([74.36, 130.4], %)
|
|> line([74.36, 130.4], %)
|
||||||
|> line([78.92, -120.11], %)
|
|> line([78.92, -120.11], %)
|
||||||
|> line([9.16, 77.79], %)
|
|> line([9.16, 77.79], %)
|
||||||
|> line([51.19, 48.97], %)
|
|> line([51.19, 48.97], %)
|
||||||
part002 = startSketchOn('XZ')
|
part002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([299.05, 231.45], %)
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|> xLine(-425.34, %, $seg_what)
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|> yLine(-264.06, %)
|
|> yLine(-264.06, %)
|
||||||
|> xLine(segLen(seg_what), %)
|
|> xLine(segLen(seg_what), %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const line3 = await u.getSegmentBodyCoords(
|
const line3 = await u.getSegmentBodyCoords(
|
||||||
`[data-overlay-index="${2}"]`
|
`[data-overlay-index="${2}"]`
|
||||||
)
|
)
|
||||||
@ -621,7 +668,7 @@ part002 = startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, constraint } of cases) {
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
test(`${testName}`, async ({ page }) => {
|
test(`${testName}`, async ({ context, homePage, page }) => {
|
||||||
// constants and locators
|
// constants and locators
|
||||||
const cmdBarKclInput = page
|
const cmdBarKclInput = page
|
||||||
.getByTestId('cmd-bar-arg-value')
|
.getByTestId('cmd-bar-arg-value')
|
||||||
@ -651,9 +698,9 @@ part002 = startSketchOn('XZ')
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
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 page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -709,33 +756,37 @@ part002 = startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { codeAfter, constraintName } of cases) {
|
for (const { codeAfter, constraintName } of cases) {
|
||||||
test(`${constraintName}`, async ({ page }) => {
|
test(`${constraintName}`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async (customCode) => {
|
await page.addInitScript(async (customCode) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 5
|
`yo = 5
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> line([74.36, 130.4], %)
|
|> line([74.36, 130.4], %)
|
||||||
|> line([78.92, -120.11], %)
|
|> line([78.92, -120.11], %)
|
||||||
|> line([9.16, 77.79], %)
|
|> line([9.16, 77.79], %)
|
||||||
|> line([51.19, 48.97], %)
|
|> line([51.19, 48.97], %)
|
||||||
part002 = startSketchOn('XZ')
|
part002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([299.05, 231.45], %)
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|> xLine(-425.34, %, $seg_what)
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|> yLine(-264.06, %)
|
|> yLine(-264.06, %)
|
||||||
|> xLine(segLen(seg_what), %)
|
|> xLine(segLen(seg_what), %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const line1 = await u.getSegmentBodyCoords(
|
const line1 = await u.getSegmentBodyCoords(
|
||||||
`[data-overlay-index="${0}"]`
|
`[data-overlay-index="${0}"]`
|
||||||
)
|
)
|
||||||
@ -754,8 +805,8 @@ part002 = startSketchOn('XZ')
|
|||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
// check actives lines
|
// check actives lines
|
||||||
|
await pollEditorLinesSelectedLength(page, codeAfter.length)
|
||||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
await expect(activeLinesContent).toHaveLength(codeAfter.length)
|
|
||||||
|
|
||||||
const constraintMenuButton = page.getByRole('button', {
|
const constraintMenuButton = page.getByRole('button', {
|
||||||
name: 'Length: open menu',
|
name: 'Length: open menu',
|
||||||
@ -806,32 +857,36 @@ part002 = startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { codeAfter, constraintName } of cases) {
|
for (const { codeAfter, constraintName } of cases) {
|
||||||
test(`${constraintName}`, async ({ page }) => {
|
test(`${constraintName}`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 5
|
`yo = 5
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> line([74.36, 130.4], %)
|
|> line([74.36, 130.4], %)
|
||||||
|> line([78.92, -120.11], %)
|
|> line([78.92, -120.11], %)
|
||||||
|> line([9.16, 77.79], %)
|
|> line([9.16, 77.79], %)
|
||||||
part002 = startSketchOn('XZ')
|
part002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([299.05, 231.45], %)
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|> xLine(-425.34, %, $seg_what)
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|> yLine(-264.06, %)
|
|> yLine(-264.06, %)
|
||||||
|> xLine(segLen(seg_what), %)
|
|> xLine(segLen(seg_what), %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const line1 = await u.getBoundingBox(`[data-overlay-index="${0}"]`)
|
const line1 = await u.getBoundingBox(`[data-overlay-index="${0}"]`)
|
||||||
const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`)
|
const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`)
|
||||||
|
|
||||||
@ -858,8 +913,8 @@ part002 = startSketchOn('XZ')
|
|||||||
// check there are still 2 cursors (they should stay on the same lines as before constraint was applied)
|
// check there are still 2 cursors (they should stay on the same lines as before constraint was applied)
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
// check actives lines
|
// check actives lines
|
||||||
|
await pollEditorLinesSelectedLength(page, 2)
|
||||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
await expect(activeLinesContent).toHaveLength(2)
|
|
||||||
|
|
||||||
// check both cursors are where they should be after constraint is applied
|
// check both cursors are where they should be after constraint is applied
|
||||||
await expect(activeLinesContent[0]).toHaveText(
|
await expect(activeLinesContent[0]).toHaveText(
|
||||||
@ -883,40 +938,47 @@ part002 = startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { codeAfter, constraintName, axisClick } of cases) {
|
for (const { codeAfter, constraintName, axisClick } of cases) {
|
||||||
test(`${constraintName}`, async ({ page }) => {
|
test(`${constraintName}`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 5
|
`yo = 5
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> line([74.36, 130.4], %)
|
|> line([74.36, 130.4], %)
|
||||||
|> line([78.92, -120.11], %)
|
|> line([78.92, -120.11], %)
|
||||||
|> line([9.16, 77.79], %)
|
|> line([9.16, 77.79], %)
|
||||||
part002 = startSketchOn('XZ')
|
part002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([299.05, 231.45], %)
|
|> startProfileAt([299.05, 231.45], %)
|
||||||
|> xLine(-425.34, %, $seg_what)
|
|> xLine(-425.34, %, $seg_what)
|
||||||
|> yLine(-264.06, %)
|
|> yLine(-264.06, %)
|
||||||
|> xLine(segLen(seg_what), %)
|
|> xLine(segLen(seg_what), %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
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.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([74.36, 130.4], %)').click()
|
await page.getByText('line([74.36, 130.4], %)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`)
|
const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`)
|
||||||
|
|
||||||
// select segment and axis by holding down shift
|
// select segment and axis by holding down shift
|
||||||
await page.mouse.click(line3.x - 3, line3.y + 20)
|
await page.mouse.click(line3.x - 3, line3.y + 20)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(axisClick.x, axisClick.y)
|
await page.mouse.click(axisClick.x, axisClick.y)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
const constraintMenuButton = page.getByRole('button', {
|
const constraintMenuButton = page.getByRole('button', {
|
||||||
name: 'Length: open menu',
|
name: 'Length: open menu',
|
||||||
})
|
})
|
||||||
@ -938,30 +1000,23 @@ part002 = startSketchOn('XZ')
|
|||||||
|
|
||||||
test('Horizontally constrained line remains selected after applying constraint', async ({
|
test('Horizontally constrained line remains selected after applying constraint', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
test.setTimeout(70_000)
|
test.setTimeout(70_000)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XY')
|
`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-1.05, -1.07], %)
|
|> startProfileAt([-1.05, -1.07], %)
|
||||||
|> line([3.79, 2.68], %, $seg01)
|
|> line([3.79, 2.68], %, $seg01)
|
||||||
|> line([3.13, -2.4], %)`
|
|> line([3.13, -2.4], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// constants and locators
|
|
||||||
const cmdBarKclInput = page
|
|
||||||
.getByTestId('cmd-bar-arg-value')
|
|
||||||
.getByRole('textbox')
|
|
||||||
const cmdBarSubmitButton = page.getByRole('button', {
|
|
||||||
name: 'arrow right Continue',
|
|
||||||
})
|
|
||||||
|
|
||||||
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.waitForPageLoad()
|
||||||
|
|
||||||
await page.getByText('line([3.79, 2.68], %, $seg01)').click()
|
await page.getByText('line([3.79, 2.68], %, $seg01)').click()
|
||||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
||||||
@ -969,6 +1024,9 @@ part002 = startSketchOn('XZ')
|
|||||||
)
|
)
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
const lineBefore = await u.getSegmentBodyCoords(
|
const lineBefore = await u.getSegmentBodyCoords(
|
||||||
`[data-overlay-index="1"]`,
|
`[data-overlay-index="1"]`,
|
||||||
@ -989,11 +1047,17 @@ part002 = startSketchOn('XZ')
|
|||||||
name: 'Length: open menu',
|
name: 'Length: open menu',
|
||||||
})
|
})
|
||||||
.click()
|
.click()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await page.getByRole('button', { name: 'Horizontal', exact: true }).click()
|
await page.getByRole('button', { name: 'Horizontal', exact: true }).click()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
await pollEditorLinesSelectedLength(page, 1)
|
||||||
let activeLinesContent = await page.locator('.cm-activeLine').all()
|
let activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
await expect(activeLinesContent[0]).toHaveText(`|> xLine(3.13, %)`)
|
await expect(activeLinesContent[0]).toHaveText(`|> xLine(3.13, %)`)
|
||||||
|
|
||||||
|
// Wait for code editor to settle.
|
||||||
|
await page.waitForTimeout(2000)
|
||||||
|
|
||||||
// If the overlay-angle is updated the THREE.js scene is in a good state
|
// If the overlay-angle is updated the THREE.js scene is in a good state
|
||||||
await expect(
|
await expect(
|
||||||
await page.locator('[data-overlay-index="1"]')
|
await page.locator('[data-overlay-index="1"]')
|
||||||
@ -1003,11 +1067,17 @@ part002 = startSketchOn('XZ')
|
|||||||
`[data-overlay-index="1"]`,
|
`[data-overlay-index="1"]`,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
expect(
|
|
||||||
await u.getGreatestPixDiff(lineAfter, TEST_COLORS.BLUE)
|
|
||||||
).toBeLessThan(3)
|
|
||||||
|
|
||||||
await page.waitForTimeout(300)
|
const linebb = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||||
|
await page.mouse.move(linebb.x, linebb.y, { steps: 25 })
|
||||||
|
await page.mouse.click(linebb.x, linebb.y)
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.poll(async () => await u.getGreatestPixDiff(lineAfter, TEST_COLORS.BLUE))
|
||||||
|
.toBeLessThan(3)
|
||||||
|
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByRole('button', {
|
.getByRole('button', {
|
||||||
name: 'Length: open menu',
|
name: 'Length: open menu',
|
||||||
@ -1018,9 +1088,10 @@ part002 = startSketchOn('XZ')
|
|||||||
// await page.getByRole('button', { name: 'length', exact: true }).click()
|
// await page.getByRole('button', { name: 'length', exact: true }).click()
|
||||||
await page.getByTestId('dropdown-constraint-length').click()
|
await page.getByTestId('dropdown-constraint-length').click()
|
||||||
|
|
||||||
await cmdBarKclInput.fill('10')
|
await page.getByLabel('length Value').fill('10')
|
||||||
await cmdBarSubmitButton.click()
|
await page.getByRole('button', { name: 'Add constraining value' }).click()
|
||||||
|
|
||||||
|
await pollEditorLinesSelectedLength(page, 1)
|
||||||
activeLinesContent = await page.locator('.cm-activeLine').all()
|
activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,9 @@
|
|||||||
import { _test, _expect } from './playwright-deprecated'
|
import { test, expect } from './zoo-test'
|
||||||
import { test } from './fixtures/fixtureSetup'
|
import { getUtils } from './test-utils'
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { TEST_CODE_GIZMO } from './storageStates'
|
import { TEST_CODE_GIZMO } from './storageStates'
|
||||||
|
|
||||||
_test.beforeEach(async ({ context, page }, testInfo) => {
|
test.describe('Testing Gizmo', () => {
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
_test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
_test.describe('Testing Gizmo', () => {
|
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testDescription: 'top view',
|
testDescription: 'top view',
|
||||||
@ -57,14 +48,17 @@ _test.describe('Testing Gizmo', () => {
|
|||||||
expectedCameraTarget,
|
expectedCameraTarget,
|
||||||
testDescription,
|
testDescription,
|
||||||
} of cases) {
|
} of cases) {
|
||||||
_test(`check ${testDescription}`, async ({ page, browserName }) => {
|
test(`check ${testDescription}`, async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript((TEST_CODE_GIZMO) => {
|
await page.addInitScript((TEST_CODE_GIZMO) => {
|
||||||
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
||||||
}, TEST_CODE_GIZMO)
|
}, TEST_CODE_GIZMO)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -117,30 +111,30 @@ _test.describe('Testing Gizmo', () => {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// position
|
// position
|
||||||
_expect(page.getByTestId('cam-x-position')).toHaveValue(
|
expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||||
expectedCameraPosition.x.toString()
|
expectedCameraPosition.x.toString()
|
||||||
),
|
),
|
||||||
_expect(page.getByTestId('cam-y-position')).toHaveValue(
|
expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||||
expectedCameraPosition.y.toString()
|
expectedCameraPosition.y.toString()
|
||||||
),
|
),
|
||||||
_expect(page.getByTestId('cam-z-position')).toHaveValue(
|
expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||||
expectedCameraPosition.z.toString()
|
expectedCameraPosition.z.toString()
|
||||||
),
|
),
|
||||||
// target
|
// target
|
||||||
_expect(page.getByTestId('cam-x-target')).toHaveValue(
|
expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||||
expectedCameraTarget.x.toString()
|
expectedCameraTarget.x.toString()
|
||||||
),
|
),
|
||||||
_expect(page.getByTestId('cam-y-target')).toHaveValue(
|
expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||||
expectedCameraTarget.y.toString()
|
expectedCameraTarget.y.toString()
|
||||||
),
|
),
|
||||||
_expect(page.getByTestId('cam-z-target')).toHaveValue(
|
expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||||
expectedCameraTarget.z.toString()
|
expectedCameraTarget.z.toString()
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_test('Context menu and popover menu', async ({ page }) => {
|
test('Context menu and popover menu', async ({ page, homePage }) => {
|
||||||
const testCase = {
|
const testCase = {
|
||||||
testDescription: 'Right view',
|
testDescription: 'Right view',
|
||||||
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
|
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
|
||||||
@ -152,9 +146,9 @@ _test.describe('Testing Gizmo', () => {
|
|||||||
await page.addInitScript((TEST_CODE_GIZMO) => {
|
await page.addInitScript((TEST_CODE_GIZMO) => {
|
||||||
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
||||||
}, TEST_CODE_GIZMO)
|
}, TEST_CODE_GIZMO)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -196,7 +190,7 @@ _test.describe('Testing Gizmo', () => {
|
|||||||
const buttonToTest = page.getByRole('button', {
|
const buttonToTest = page.getByRole('button', {
|
||||||
name: testCase.testDescription,
|
name: testCase.testDescription,
|
||||||
})
|
})
|
||||||
await _expect(buttonToTest).toBeVisible()
|
await expect(buttonToTest).toBeVisible()
|
||||||
await buttonToTest.click()
|
await buttonToTest.click()
|
||||||
|
|
||||||
// Now assert we've moved to the correct view
|
// Now assert we've moved to the correct view
|
||||||
@ -215,23 +209,23 @@ _test.describe('Testing Gizmo', () => {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// position
|
// position
|
||||||
_expect(page.getByTestId('cam-x-position')).toHaveValue(
|
expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||||
testCase.expectedCameraPosition.x.toString()
|
testCase.expectedCameraPosition.x.toString()
|
||||||
),
|
),
|
||||||
_expect(page.getByTestId('cam-y-position')).toHaveValue(
|
expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||||
testCase.expectedCameraPosition.y.toString()
|
testCase.expectedCameraPosition.y.toString()
|
||||||
),
|
),
|
||||||
_expect(page.getByTestId('cam-z-position')).toHaveValue(
|
expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||||
testCase.expectedCameraPosition.z.toString()
|
testCase.expectedCameraPosition.z.toString()
|
||||||
),
|
),
|
||||||
// target
|
// target
|
||||||
_expect(page.getByTestId('cam-x-target')).toHaveValue(
|
expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||||
testCase.expectedCameraTarget.x.toString()
|
testCase.expectedCameraTarget.x.toString()
|
||||||
),
|
),
|
||||||
_expect(page.getByTestId('cam-y-target')).toHaveValue(
|
expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||||
testCase.expectedCameraTarget.y.toString()
|
testCase.expectedCameraTarget.y.toString()
|
||||||
),
|
),
|
||||||
_expect(page.getByTestId('cam-z-target')).toHaveValue(
|
expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||||
testCase.expectedCameraTarget.z.toString()
|
testCase.expectedCameraTarget.z.toString()
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
@ -242,32 +236,59 @@ _test.describe('Testing Gizmo', () => {
|
|||||||
const gizmoPopoverButton = page.getByRole('button', {
|
const gizmoPopoverButton = page.getByRole('button', {
|
||||||
name: 'view settings',
|
name: 'view settings',
|
||||||
})
|
})
|
||||||
await _expect(gizmoPopoverButton).toBeVisible()
|
await expect(gizmoPopoverButton).toBeVisible()
|
||||||
await gizmoPopoverButton.click()
|
await gizmoPopoverButton.click()
|
||||||
await _expect(buttonToTest).toBeVisible()
|
await expect(buttonToTest).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe(`Testing gizmo, fixture-based`, () => {
|
test.describe(`Testing gizmo, fixture-based`, () => {
|
||||||
test('Center on selection from menu', async ({
|
test('Center on selection from menu', async ({
|
||||||
app,
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
cmdBar,
|
cmdBar,
|
||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
test.skip(
|
await context.addInitScript(() => {
|
||||||
process.platform === 'win32',
|
localStorage.setItem(
|
||||||
'Fails on windows in CI, can not be replicated locally on windows.'
|
'persistCode',
|
||||||
)
|
`
|
||||||
|
const sketch002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-108.83, -57.48], %)
|
||||||
|
|> angledLine([0, 105.13], %, $rectangleSegmentA001)
|
||||||
|
|> angledLine([
|
||||||
|
segAng(rectangleSegmentA001) - 90,
|
||||||
|
77.9
|
||||||
|
], %)
|
||||||
|
|> angledLine([
|
||||||
|
segAng(rectangleSegmentA001),
|
||||||
|
-segLen(rectangleSegmentA001)
|
||||||
|
], %)
|
||||||
|
|> close(%)
|
||||||
|
const sketch001 = startSketchOn('XZ')
|
||||||
|
|> circle({
|
||||||
|
center: [818.33, 168.1],
|
||||||
|
radius: 182.8
|
||||||
|
}, %)
|
||||||
|
|> extrude(50, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await test.step(`Setup`, async () => {
|
await test.step(`Setup`, async () => {
|
||||||
const file = await app.getInputFile('test-circle-extrude.kcl')
|
|
||||||
await app.initialise(file)
|
|
||||||
await scene.expectState({
|
await scene.expectState({
|
||||||
camera: {
|
camera: {
|
||||||
position: [4982.21, -23865.37, 13810.64],
|
position: [11912.6, -39586.98, 21391.21],
|
||||||
target: [4982.21, 0, 2737.1],
|
target: [11912.6, -635, 3317.49],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -275,7 +296,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
|||||||
|
|
||||||
await test.step(`Select an edge of this circle`, async () => {
|
await test.step(`Select an edge of this circle`, async () => {
|
||||||
const circleSnippet =
|
const circleSnippet =
|
||||||
'circle({ center = [318.33, 168.1], radius = 182.8 }, %)'
|
'circle({ center: [818.33, 168.1], radius: 182.8 }, %)'
|
||||||
await moveToCircle()
|
await moveToCircle()
|
||||||
await clickCircle()
|
await clickCircle()
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
@ -292,8 +313,8 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
|||||||
await test.step(`Verify the camera moved`, async () => {
|
await test.step(`Verify the camera moved`, async () => {
|
||||||
await scene.expectState({
|
await scene.expectState({
|
||||||
camera: {
|
camera: {
|
||||||
position: [0, -23865.37, 11073.53],
|
position: [20785.58, -40221.98, 22343.46],
|
||||||
target: [0, 0, 0],
|
target: [20785.58, -1270, 4269.74],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,18 +1,8 @@
|
|||||||
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 { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
|
|
||||||
import * as TOML from '@iarna/toml'
|
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Test toggling perspective', () => {
|
test.describe('Test toggling perspective', () => {
|
||||||
test('via command palette and toggle', async ({ page }) => {
|
test.fixme('via command palette and toggle', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Locators and constants
|
// Locators and constants
|
||||||
@ -20,7 +10,7 @@ test.describe('Test toggling perspective', () => {
|
|||||||
const screenHeight = 500
|
const screenHeight = 500
|
||||||
const checkedScreenLocation = {
|
const checkedScreenLocation = {
|
||||||
x: screenWidth * 0.71,
|
x: screenWidth * 0.71,
|
||||||
y: screenHeight * 0.4,
|
y: screenHeight * 0.2,
|
||||||
}
|
}
|
||||||
const backgroundColor: [number, number, number] = [29, 29, 29]
|
const backgroundColor: [number, number, number] = [29, 29, 29]
|
||||||
const xzPlaneColor: [number, number, number] = [82, 55, 96]
|
const xzPlaneColor: [number, number, number] = [82, 55, 96]
|
||||||
@ -40,8 +30,8 @@ test.describe('Test toggling perspective', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Setup', async () => {
|
await test.step('Setup', async () => {
|
||||||
await page.setViewportSize({ width: screenWidth, height: screenHeight })
|
await page.setBodyDimensions({ width: screenWidth, height: screenHeight })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
await expect
|
await expect
|
||||||
.poll(async () => locationToHaveColor(backgroundColor), {
|
.poll(async () => locationToHaveColor(backgroundColor), {
|
||||||
@ -52,11 +42,17 @@ test.describe('Test toggling perspective', () => {
|
|||||||
await expect(projectionToggle).toHaveAttribute('aria-checked', 'true')
|
await expect(projectionToggle).toHaveAttribute('aria-checked', 'true')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Extremely wild note: flicking between ortho and persp actually changes
|
||||||
|
// the orientation of the axis/camera. How can you see this? Well toggle it,
|
||||||
|
// then refresh. You'll see it doesn't match what we left.
|
||||||
await test.step('Switch to ortho via command palette', async () => {
|
await test.step('Switch to ortho via command palette', async () => {
|
||||||
await commandPaletteButton.click()
|
await commandPaletteButton.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await commandOption.click()
|
await commandOption.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await orthoOption.click()
|
await orthoOption.click()
|
||||||
await expect(commandToast).toBeVisible()
|
await expect(commandToast).toBeVisible()
|
||||||
|
await expect(commandToast).not.toBeVisible()
|
||||||
await expect
|
await expect
|
||||||
.poll(async () => locationToHaveColor(xzPlaneColor), {
|
.poll(async () => locationToHaveColor(xzPlaneColor), {
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
@ -67,27 +63,9 @@ test.describe('Test toggling perspective', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Refresh the page and ensure the stream is loaded in ortho`, async () => {
|
await test.step(`Refresh the page and ensure the stream is loaded in ortho`, async () => {
|
||||||
// In playwright web, the settings set while testing are not persisted because
|
|
||||||
// the `addInitScript` within `setup` is re-run on page reload
|
|
||||||
await page.addInitScript(
|
|
||||||
({ settingsKey, settings }) => {
|
|
||||||
localStorage.setItem(settingsKey, settings)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
|
||||||
settings: TOML.stringify({
|
|
||||||
settings: {
|
|
||||||
...TEST_SETTINGS,
|
|
||||||
modeling: {
|
|
||||||
...TEST_SETTINGS.modeling,
|
|
||||||
cameraProjection: 'orthographic',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
await page.reload()
|
await page.reload()
|
||||||
await u.waitForAuthSkipAppStart()
|
await page.waitForTimeout(1000)
|
||||||
|
await u.closeKclCodePanel()
|
||||||
await expect
|
await expect
|
||||||
.poll(async () => locationToHaveColor(xzPlaneColor), {
|
.poll(async () => locationToHaveColor(xzPlaneColor), {
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
|
|||||||
@ -1,35 +1,30 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { FILE_EXT } from 'lib/constants'
|
import { FILE_EXT } from 'lib/constants'
|
||||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Testing in-app sample loading', () => {
|
test.describe('Testing in-app sample loading', () => {
|
||||||
/**
|
/**
|
||||||
* Note this test implicitly depends on the KCL sample "car-wheel.kcl",
|
* Note this test implicitly depends on the KCL sample "car-wheel.kcl",
|
||||||
* its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/car-wheel/car-wheel.kcl
|
* its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/car-wheel/car-wheel.kcl
|
||||||
*/
|
*/
|
||||||
test('Web: should overwrite current code, cannot create new file', async ({
|
test('Web: should overwrite current code, cannot create new file', async ({
|
||||||
|
editor,
|
||||||
|
context,
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await test.step(`Test setup`, async () => {
|
await test.step(`Test setup`, async () => {
|
||||||
await page.addInitScript((code) => {
|
await context.addInitScript((code) => {
|
||||||
window.localStorage.setItem('persistCode', code)
|
window.localStorage.setItem('persistCode', code)
|
||||||
}, bracket)
|
}, bracket)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Locators and constants
|
// Locators and constants
|
||||||
@ -54,13 +49,13 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
})
|
})
|
||||||
const warningText = page.getByText('Overwrite current file and units?')
|
const warningText = page.getByText('Overwrite current file and units?')
|
||||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
||||||
const codeLocator = page.locator('.cm-content')
|
|
||||||
const unitsToast = (unit: UnitLength_type) =>
|
const unitsToast = (unit: UnitLength_type) =>
|
||||||
page.getByText(`Set default unit to "${unit}" for this project`)
|
page.getByText(`Set default unit to "${unit}" for this project`)
|
||||||
|
|
||||||
await test.step(`Precondition: check the initial code`, async () => {
|
await test.step(`Precondition: check the initial code`, async () => {
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await expect(codeLocator).toContainText(bracket.split('\n')[0])
|
await editor.scrollToText(bracket.split('\n')[0])
|
||||||
|
await editor.expectEditor.toContain(bracket.split('\n')[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||||
@ -73,7 +68,7 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
await expect(warningText).toBeVisible()
|
await expect(warningText).toBeVisible()
|
||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
|
|
||||||
await expect(codeLocator).toContainText('// ' + newSample.title)
|
await editor.expectEditor.toContain('// ' + newSample.title)
|
||||||
await expect(unitsToast('in')).toBeVisible()
|
await expect(unitsToast('in')).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -86,16 +81,13 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
test(
|
test(
|
||||||
'Desktop: should create new file by default, optionally overwrite',
|
'Desktop: should create new file by default, optionally overwrite',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName: _ }, testInfo) => {
|
async ({ editor, context, page }, testInfo) => {
|
||||||
const { electronApp, page, dir } = await setupElectron({
|
const { dir } = 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.writeFile(join(bracketDir, 'main.kcl'), bracket, {
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
encoding: 'utf-8',
|
||||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), bracket, {
|
})
|
||||||
encoding: 'utf-8',
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -134,19 +126,19 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
page.getByRole('listitem').filter({
|
page.getByRole('listitem').filter({
|
||||||
has: page.getByRole('button', { name }),
|
has: page.getByRole('button', { name }),
|
||||||
})
|
})
|
||||||
const codeLocator = page.locator('.cm-content')
|
|
||||||
const unitsToast = (unit: UnitLength_type) =>
|
const unitsToast = (unit: UnitLength_type) =>
|
||||||
page.getByText(`Set default unit to "${unit}" for this project`)
|
page.getByText(`Set default unit to "${unit}" for this project`)
|
||||||
|
|
||||||
await test.step(`Test setup`, async () => {
|
await test.step(`Test setup`, async () => {
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await projectCard.click()
|
await projectCard.click()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Precondition: check the initial code`, async () => {
|
await test.step(`Precondition: check the initial code`, async () => {
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await expect(codeLocator).toContainText(bracket.split('\n')[0])
|
await editor.scrollToText(bracket.split('\n')[0])
|
||||||
|
await editor.expectEditor.toContain(bracket.split('\n')[0])
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
|
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
@ -163,7 +155,7 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Ensure we made and opened a new file`, async () => {
|
await test.step(`Ensure we made and opened a new file`, async () => {
|
||||||
await expect(codeLocator).toContainText('// ' + sampleOne.title)
|
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
||||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||||
await expect(unitsToast('in')).toBeVisible()
|
await expect(unitsToast('in')).toBeVisible()
|
||||||
@ -182,7 +174,7 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Ensure we overwrote the current file without navigating`, async () => {
|
await test.step(`Ensure we overwrote the current file without navigating`, async () => {
|
||||||
await expect(codeLocator).toContainText('// ' + sampleTwo.title)
|
await editor.expectEditor.toContain('// ' + sampleTwo.title)
|
||||||
await test.step(`Check actual file contents`, async () => {
|
await test.step(`Check actual file contents`, async () => {
|
||||||
await expect
|
await expect
|
||||||
.poll(async () => {
|
.poll(async () => {
|
||||||
@ -198,8 +190,6 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||||
await expect(unitsToast('mm')).toBeVisible()
|
await expect(unitsToast('mm')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,24 +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'
|
||||||
import { Coords2d } from 'lang/std/sketch'
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
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('Testing selections', () => {
|
test.describe('Testing selections', () => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
test(
|
test(
|
||||||
'Selections work on fresh and edited sketch',
|
'Selections work on fresh and edited sketch',
|
||||||
{ tag: ['@skipWin'] },
|
{ tag: ['@skipWin'] },
|
||||||
async ({ page }) => {
|
async ({ page, homePage }) => {
|
||||||
// Skip on windows its being weird.
|
// Skip on windows its being weird.
|
||||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
|
|
||||||
@ -27,9 +19,9 @@ test.describe('Testing selections', () => {
|
|||||||
// source ranges are wrong, hovers won't work
|
// source ranges are wrong, hovers won't work
|
||||||
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 yAxisClick = () =>
|
const yAxisClick = () =>
|
||||||
@ -79,31 +71,31 @@ test.describe('Testing selections', () => {
|
|||||||
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)
|
||||||
|
|
||||||
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}, %)`)
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
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}, %)
|
||||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
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}, %)
|
||||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
@ -264,78 +256,78 @@ test.describe('Testing selections', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test('Solids should be select and deletable', async ({ page }) => {
|
test('Solids should be select and deletable', async ({ page, homePage }) => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
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([-79.26, 95.04], %)
|
|> startProfileAt([-79.26, 95.04], %)
|
||||||
|> line([112.54, 127.64], %, $seg02)
|
|> line([112.54, 127.64], %, $seg02)
|
||||||
|> line([170.36, -121.61], %, $seg01)
|
|> line([170.36, -121.61], %, $seg01)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(50, sketch001)
|
extrude001 = extrude(50, sketch001)
|
||||||
sketch005 = startSketchOn(extrude001, 'END')
|
sketch005 = startSketchOn(extrude001, 'END')
|
||||||
|> startProfileAt([23.24, 136.52], %)
|
|> startProfileAt([23.24, 136.52], %)
|
||||||
|> line([-8.44, 36.61], %)
|
|> line([-8.44, 36.61], %)
|
||||||
|> line([49.4, 2.05], %)
|
|> line([49.4, 2.05], %)
|
||||||
|> line([29.69, -46.95], %)
|
|> line([29.69, -46.95], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
sketch003 = startSketchOn(extrude001, seg01)
|
sketch003 = startSketchOn(extrude001, seg01)
|
||||||
|> startProfileAt([21.23, 17.81], %)
|
|> startProfileAt([21.23, 17.81], %)
|
||||||
|> line([51.97, 21.32], %)
|
|> line([51.97, 21.32], %)
|
||||||
|> line([4.07, -22.75], %)
|
|> line([4.07, -22.75], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
sketch002 = startSketchOn(extrude001, seg02)
|
sketch002 = startSketchOn(extrude001, seg02)
|
||||||
|> startProfileAt([-100.54, 16.99], %)
|
|> startProfileAt([-100.54, 16.99], %)
|
||||||
|> line([0, 20.03], %)
|
|> line([0, 20.03], %)
|
||||||
|> line([62.61, 0], %, $seg03)
|
|> line([62.61, 0], %, $seg03)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude002 = extrude(50, sketch002)
|
extrude002 = extrude(50, sketch002)
|
||||||
sketch004 = startSketchOn(extrude002, seg03)
|
sketch004 = startSketchOn(extrude002, seg03)
|
||||||
|> startProfileAt([57.07, 134.77], %)
|
|> startProfileAt([57.07, 134.77], %)
|
||||||
|> line([-4.72, 22.84], %)
|
|> line([-4.72, 22.84], %)
|
||||||
|> line([28.8, 6.71], %)
|
|> line([28.8, 6.71], %)
|
||||||
|> line([9.19, -25.33], %)
|
|> line([9.19, -25.33], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude003 = extrude(20, sketch004)
|
extrude003 = extrude(20, sketch004)
|
||||||
pipeLength = 40
|
pipeLength = 40
|
||||||
pipeSmallDia = 10
|
pipeSmallDia = 10
|
||||||
pipeLargeDia = 20
|
pipeLargeDia = 20
|
||||||
thickness = 0.5
|
thickness = 0.5
|
||||||
part009 = startSketchOn('XY')
|
part009 = startSketchOn('XY')
|
||||||
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|
||||||
|> line([thickness, 0], %)
|
|> line([thickness, 0], %)
|
||||||
|> line([0, -1], %)
|
|> line([0, -1], %)
|
||||||
|> angledLineToX({
|
|> angledLineToX({
|
||||||
angle = 60,
|
angle = 60,
|
||||||
to = pipeSmallDia + thickness
|
to = pipeSmallDia + thickness
|
||||||
}, %)
|
}, %)
|
||||||
|> line([0, -pipeLength], %)
|
|> line([0, -pipeLength], %)
|
||||||
|> angledLineToX({
|
|> angledLineToX({
|
||||||
angle = -60,
|
angle = -60,
|
||||||
to = pipeLargeDia + thickness
|
to = pipeLargeDia + thickness
|
||||||
}, %)
|
}, %)
|
||||||
|> line([0, -1], %)
|
|> line([0, -1], %)
|
||||||
|> line([-thickness, 0], %)
|
|> line([-thickness, 0], %)
|
||||||
|> line([0, 1], %)
|
|> line([0, 1], %)
|
||||||
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|
||||||
|> line([0, pipeLength], %)
|
|> line([0, pipeLength], %)
|
||||||
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
rev = revolve({ axis = 'y' }, part009)
|
rev = revolve({ axis: 'y' }, part009)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
}, KCL_DEFAULT_LENGTH)
|
}, KCL_DEFAULT_LENGTH)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
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"]')
|
||||||
@ -395,29 +387,29 @@ rev = revolve({ axis = 'y' }, part009)
|
|||||||
`extrude001 = extrude(50, sketch001)`
|
`extrude001 = extrude(50, sketch001)`
|
||||||
)
|
)
|
||||||
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = 0, y = -50, z = 0 },
|
origin = { x = 0, y = -50, z = 0 },
|
||||||
x_axis = { x = 1, y = 0, z = 0 },
|
x_axis = { x = 1, y = 0, z = 0 },
|
||||||
y_axis = { x = 0, y = 0, z = 1 },
|
y_axis = { x = 0, y = 0, z = 1 },
|
||||||
z_axis = { x = 0, y = -1, z = 0 }
|
z_axis = { x = 0, y = -1, z = 0 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = 116.53, y = 0, z = 163.25 },
|
origin = { x = 116.53, y = 0, z = 163.25 },
|
||||||
x_axis = { x = -0.81, y = 0, z = 0.58 },
|
x_axis = { x = -0.81, y = 0, z = 0.58 },
|
||||||
y_axis = { x = 0, y = -1, z = 0 },
|
y_axis = { x = 0, y = -1, z = 0 },
|
||||||
z_axis = { x = 0.58, y = 0, z = 0.81 }
|
z_axis = { x = 0.58, y = 0, z = 0.81 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = -91.74, y = 0, z = 80.89 },
|
origin = { x = -91.74, y = 0, z = 80.89 },
|
||||||
x_axis = { x = -0.66, y = 0, z = -0.75 },
|
x_axis = { x = -0.66, y = 0, z = -0.75 },
|
||||||
y_axis = { x = 0, y = -1, z = 0 },
|
y_axis = { x = 0, y = -1, z = 0 },
|
||||||
z_axis = { x = -0.75, y = 0, z = 0.66 }
|
z_axis = { x = -0.75, y = 0, z = 0.66 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
|
|
||||||
// DELETE SOLID 2D
|
// DELETE SOLID 2D
|
||||||
await page.mouse.click(solid2d.x, solid2d.y)
|
await page.mouse.click(solid2d.x, solid2d.y)
|
||||||
@ -433,31 +425,32 @@ rev = revolve({ axis = 'y' }, part009)
|
|||||||
})
|
})
|
||||||
test("Deleting solid that the AST mod can't handle results in a toast message", async ({
|
test("Deleting solid that the AST mod can't handle results in a toast message", 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('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-79.26, 95.04], %)
|
|> startProfileAt([-79.26, 95.04], %)
|
||||||
|> line([112.54, 127.64], %, $seg02)
|
|> line([112.54, 127.64], %, $seg02)
|
||||||
|> line([170.36, -121.61], %, $seg01)
|
|> line([170.36, -121.61], %, $seg01)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(50, sketch001)
|
extrude001 = extrude(50, sketch001)
|
||||||
launderExtrudeThroughVar = extrude001
|
launderExtrudeThroughVar = extrude001
|
||||||
sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
||||||
|> startProfileAt([-100.54, 16.99], %)
|
|> startProfileAt([-100.54, 16.99], %)
|
||||||
|> line([0, 20.03], %)
|
|> line([0, 20.03], %)
|
||||||
|> line([62.61, 0], %, $seg03)
|
|> line([62.61, 0], %, $seg03)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
}, KCL_DEFAULT_LENGTH)
|
}, KCL_DEFAULT_LENGTH)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||||
@ -497,37 +490,38 @@ sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
|||||||
})
|
})
|
||||||
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`part001 = startSketchOn('XZ')
|
`part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([20, 0], %)
|
|> startProfileAt([20, 0], %)
|
||||||
|> line([7.13, 4 + 0], %)
|
|> line([7.13, 4 + 0], %)
|
||||||
|> angledLine({ angle = 3 + 0, length = 3.14 + 0 }, %)
|
|> angledLine({ angle = 3 + 0, length = 3.14 + 0 }, %)
|
||||||
|> lineTo([20.14 + 0, -0.14 + 0], %)
|
|> lineTo([20.14 + 0, -0.14 + 0], %)
|
||||||
|> xLineTo(29 + 0, %)
|
|> xLineTo(29 + 0, %)
|
||||||
|> yLine(-3.14 + 0, %, $a)
|
|> yLine(-3.14 + 0, %, $a)
|
||||||
|> xLine(1.63, %)
|
|> xLine(1.63, %)
|
||||||
|> angledLineOfXLength({ angle = 3 + 0, length = 3.14 }, %)
|
|> angledLineOfXLength({ angle = 3 + 0, length = 3.14 }, %)
|
||||||
|> angledLineOfYLength({ angle = 30, length = 3 + 0 }, %)
|
|> angledLineOfYLength({ angle = 30, length = 3 + 0 }, %)
|
||||||
|> angledLineToX({ angle = 22.14 + 0, to = 12 }, %)
|
|> angledLineToX({ angle = 22.14 + 0, to = 12 }, %)
|
||||||
|> angledLineToY({ angle = 30, to = 11.14 }, %)
|
|> angledLineToY({ angle = 30, to = 11.14 }, %)
|
||||||
|> angledLineThatIntersects({
|
|> angledLineThatIntersects({
|
||||||
angle = 3.14,
|
angle = 3.14,
|
||||||
intersectTag = a,
|
intersectTag = a,
|
||||||
offset = 0
|
offset = 0
|
||||||
}, %)
|
}, %)
|
||||||
|> tangentialArcTo([13.14 + 0, 13.14], %)
|
|> tangentialArcTo([13.14 + 0, 13.14], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5 + 7, %)
|
|> extrude(5 + 7, %)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
}, KCL_DEFAULT_LENGTH)
|
}, KCL_DEFAULT_LENGTH)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -650,7 +644,7 @@ sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
|||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'flatExtrusionFace',
|
'flatExtrusionFace',
|
||||||
flatExtrusionFace,
|
flatExtrusionFace,
|
||||||
`angledLineThatIntersects({angle=3.14,intersectTag=a,offset=0},%)extrude(5+7,%)`,
|
`angledLineThatIntersects({angle:3.14,intersectTag:a,offset:0},%)extrude(5+7,%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -707,19 +701,19 @@ sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
|||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'straightSegmentEdge',
|
'straightSegmentEdge',
|
||||||
straightSegmentEdge,
|
straightSegmentEdge,
|
||||||
`angledLineToY({angle=30,to=11.14},%)`,
|
`angledLineToY({angle:30,to:11.14},%)`,
|
||||||
'angledLineToY({ angle = 30, to = 11.14 }, %)'
|
'angledLineToY({ angle: 30, to: 11.14 }, %)'
|
||||||
)
|
)
|
||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'straightSegmentOppositeEdge',
|
'straightSegmentOppositeEdge',
|
||||||
straightSegmentOppositeEdge,
|
straightSegmentOppositeEdge,
|
||||||
`angledLineToY({angle=30,to=11.14},%)`,
|
`angledLineToY({angle:30,to:11.14},%)`,
|
||||||
'angledLineToY({ angle = 30, to = 11.14 }, %)'
|
'angledLineToY({ angle: 30, to: 11.14 }, %)'
|
||||||
)
|
)
|
||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'straightSegmentAdjacentEdge',
|
'straightSegmentAdjacentEdge',
|
||||||
straightSegmentAdjacentEdge,
|
straightSegmentAdjacentEdge,
|
||||||
`angledLineThatIntersects({angle=3.14,intersectTag=a,offset=0},%)`,
|
`angledLineThatIntersects({angle:3.14,intersectTag:a,offset:0},%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -727,29 +721,29 @@ sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
|||||||
|
|
||||||
await u.removeCurrentCode()
|
await u.removeCurrentCode()
|
||||||
await u.codeLocator.fill(`sketch001 = startSketchOn('XZ')
|
await u.codeLocator.fill(`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)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001) - 90,
|
segAng(rectangleSegmentA001) - 90,
|
||||||
217.26
|
217.26
|
||||||
], %, $seg01)
|
], %, $seg01)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001)
|
-segLen(rectangleSegmentA001)
|
||||||
], %, $yo)
|
], %, $yo)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %, $seg02)
|
|> lineTo([profileStartX(%), profileStartY(%)], %, $seg02)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(100, sketch001)
|
extrude001 = extrude(100, sketch001)
|
||||||
|> chamfer({
|
|> chamfer({
|
||||||
length = 30,
|
length = 30,
|
||||||
tags = [
|
tags = [
|
||||||
seg01,
|
seg01,
|
||||||
getNextAdjacentEdge(yo),
|
getNextAdjacentEdge(yo),
|
||||||
getNextAdjacentEdge(seg02),
|
getNextAdjacentEdge(seg02),
|
||||||
getOppositeEdge(seg01)
|
getOppositeEdge(seg01)
|
||||||
]
|
]
|
||||||
}, %)
|
}, %)
|
||||||
`)
|
`)
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('model-state-indicator-execution-done')
|
page.getByTestId('model-state-indicator-execution-done')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
@ -786,14 +780,14 @@ extrude001 = extrude(100, sketch001)
|
|||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'oppositeChamfer',
|
'oppositeChamfer',
|
||||||
oppositeChamfer,
|
oppositeChamfer,
|
||||||
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer({length:30,tags:[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
|
|
||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'baseChamfer',
|
'baseChamfer',
|
||||||
baseChamfer,
|
baseChamfer,
|
||||||
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer({length:30,tags:[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -824,52 +818,58 @@ extrude001 = extrude(100, sketch001)
|
|||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'adjacentChamfer1',
|
'adjacentChamfer1',
|
||||||
adjacentChamfer1,
|
adjacentChamfer1,
|
||||||
`lineTo([profileStartX(%),profileStartY(%)],%,$seg02)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
`lineTo([profileStartX(%),profileStartY(%)],%,$seg02)chamfer({length:30,tags:[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
|
|
||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'adjacentChamfer2',
|
'adjacentChamfer2',
|
||||||
adjacentChamfer2,
|
adjacentChamfer2,
|
||||||
`angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
`angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)chamfer({length:30,tags:[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
test("Extrude button should be disabled if there's no extrudable geometry when nothing is selected", async ({
|
test("Extrude button should be disabled if there's no extrudable geometry when nothing is selected", async ({
|
||||||
page,
|
page,
|
||||||
|
editor,
|
||||||
|
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], %)
|
||||||
|> line([3.75, 0.46], %)
|
|> line([3.75, 0.46], %)
|
||||||
|> line([4.99, -0.46], %, $seg01)
|
|> line([4.99, -0.46], %, $seg01)
|
||||||
|> line([3.3, -2.12], %)
|
|> line([3.3, -2.12], %)
|
||||||
|> line([2.16, -3.33], %)
|
|> line([2.16, -3.33], %)
|
||||||
|> line([0.85, -3.08], %)
|
|> line([0.85, -3.08], %)
|
||||||
|> line([-0.18, -3.36], %)
|
|> line([-0.18, -3.36], %)
|
||||||
|> line([-3.86, -2.73], %)
|
|> line([-3.86, -2.73], %)
|
||||||
|> line([-17.67, 0.85], %)
|
|> line([-17.67, 0.85], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(10, sketch001)
|
extrude001 = extrude(10, sketch001)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
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()
|
||||||
|
|
||||||
const selectUnExtrudable = () =>
|
const selectUnExtrudable = async () => {
|
||||||
page.getByText(`line([4.99, -0.46], %, $seg01)`).click()
|
await editor.scrollToText(`line([4.99, -0.46], %, $seg01)`)
|
||||||
|
await page.getByText(`line([4.99, -0.46], %, $seg01)`).click()
|
||||||
|
}
|
||||||
const clickEmpty = () => page.mouse.click(700, 460)
|
const clickEmpty = () => page.mouse.click(700, 460)
|
||||||
await selectUnExtrudable()
|
await selectUnExtrudable()
|
||||||
// expect extrude button to be disabled
|
// expect extrude button to be disabled
|
||||||
@ -879,17 +879,18 @@ extrude001 = extrude(10, sketch001)
|
|||||||
|
|
||||||
// expect active line to contain nothing
|
// expect active line to contain nothing
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText('')
|
await expect(page.locator('.cm-activeLine')).toHaveText('')
|
||||||
|
|
||||||
// and extrude to still be disabled
|
// and extrude to still be disabled
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||||
|
|
||||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||||
sketch002 = startSketchOn(extrude001, $seg01)
|
sketch002 = startSketchOn(extrude001, $seg01)
|
||||||
|> startProfileAt([-12.94, 6.6], %)
|
|> startProfileAt([-12.94, 6.6], %)
|
||||||
|> line([2.45, -0.2], %)
|
|> line([2.45, -0.2], %)
|
||||||
|> line([-2, -1.25], %)
|
|> line([-2, -1.25], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`
|
`
|
||||||
await u.codeLocator.fill(codeToAdd)
|
await u.codeLocator.fill(codeToAdd)
|
||||||
|
|
||||||
await selectUnExtrudable()
|
await selectUnExtrudable()
|
||||||
@ -904,23 +905,23 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
|||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Fillet button states test', async ({ page }) => {
|
test('Fillet button states test', 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([-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(%)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
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()
|
||||||
@ -937,7 +938,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
|||||||
|
|
||||||
// test fillet button with the body in the scene
|
// test fillet button with the body in the scene
|
||||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||||
extrude001 = extrude(10, sketch001)`
|
extrude001 = extrude(10, sketch001)`
|
||||||
await u.codeLocator.clear()
|
await u.codeLocator.clear()
|
||||||
await u.codeLocator.fill(codeToAdd)
|
await u.codeLocator.fill(codeToAdd)
|
||||||
await selectSegment()
|
await selectSegment()
|
||||||
@ -958,6 +959,7 @@ extrude001 = extrude(10, sketch001)`
|
|||||||
|
|
||||||
test('Testing selections (and hovers) work on sketches when NOT in sketch mode', async ({
|
test('Testing selections (and hovers) work on sketches when NOT in sketch mode', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
@ -978,21 +980,21 @@ extrude001 = extrude(10, sketch001)`
|
|||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yo = 79
|
`yo = 79
|
||||||
part001 = startSketchOn('XZ')
|
part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-7.54, -26.74], %)
|
|> startProfileAt([-7.54, -26.74], %)
|
||||||
|> ${cases[0].expectedCode}
|
|> ${cases[0].expectedCode}
|
||||||
|> line([-3.19, -138.43], %)
|
|> line([-3.19, -138.43], %)
|
||||||
|> ${cases[1].expectedCode}
|
|> ${cases[1].expectedCode}
|
||||||
|> line([41.19, 28.97 + 5], %)
|
|> line([41.19, 28.97 + 5], %)
|
||||||
|> ${cases[2].expectedCode}`
|
|> ${cases[2].expectedCode}`
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ cases }
|
{ cases }
|
||||||
)
|
)
|
||||||
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.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
@ -1025,24 +1027,25 @@ part001 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
test("Hovering and selection of extruded faces works, and is not overridden shortly after user's click", async ({
|
test("Hovering and selection of extruded faces works, and is not overridden shortly after user's click", async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-79.26, 95.04], %)
|
|> startProfileAt([-79.26, 95.04], %)
|
||||||
|> line([112.54, 127.64], %)
|
|> line([112.54, 127.64], %)
|
||||||
|> line([170.36, -121.61], %, $seg01)
|
|> line([170.36, -121.61], %, $seg01)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(50, sketch001)
|
extrude001 = extrude(50, sketch001)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
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.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
@ -1125,6 +1128,7 @@ extrude001 = extrude(50, sketch001)
|
|||||||
})
|
})
|
||||||
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const selectionsSnippets = {
|
const selectionsSnippets = {
|
||||||
@ -1143,46 +1147,46 @@ extrude001 = extrude(50, sketch001)
|
|||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`part001 = startSketchOn('XZ')
|
`part001 = startSketchOn('XZ')
|
||||||
${extrudeAndEditBlocked}
|
${extrudeAndEditBlocked}
|
||||||
|> line([25.96, 2.93], %)
|
|> line([25.96, 2.93], %)
|
||||||
|> line([5.25, -5.72], %)
|
|> line([5.25, -5.72], %)
|
||||||
|> line([-2.01, -10.35], %)
|
|> line([-2.01, -10.35], %)
|
||||||
|> line([-27.65, -2.78], %)
|
|> line([-27.65, -2.78], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
sketch002 = startSketchOn('XZ')
|
sketch002 = startSketchOn('XZ')
|
||||||
${extrudeAndEditAllowed}
|
${extrudeAndEditAllowed}
|
||||||
|> line([10.32, 6.47], %)
|
|> line([10.32, 6.47], %)
|
||||||
|> line([9.71, -6.16], %)
|
|> line([9.71, -6.16], %)
|
||||||
|> line([-3.08, -9.86], %)
|
|> line([-3.08, -9.86], %)
|
||||||
|> line([-12.02, -1.54], %)
|
|> line([-12.02, -1.54], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
sketch003 = startSketchOn('XZ')
|
sketch003 = startSketchOn('XZ')
|
||||||
${editOnly}
|
${editOnly}
|
||||||
|> line([27.55, -1.65], %)
|
|> line([27.55, -1.65], %)
|
||||||
|> line([4.95, -8], %)
|
|> line([4.95, -8], %)
|
||||||
|> line([-20.38, -10.12], %)
|
|> line([-20.38, -10.12], %)
|
||||||
|> line([-15.79, 17.08], %)
|
|> line([-15.79, 17.08], %)
|
||||||
|
|
||||||
fn yohey = (pos) => {
|
fn yohey = (pos) => {
|
||||||
sketch004 = startSketchOn('XZ')
|
sketch004 = startSketchOn('XZ')
|
||||||
${extrudeAndEditBlockedInFunction}
|
${extrudeAndEditBlockedInFunction}
|
||||||
|> line([27.55, -1.65], %)
|
|> line([27.55, -1.65], %)
|
||||||
|> line([4.95, -10.53], %)
|
|> line([4.95, -10.53], %)
|
||||||
|> line([-20.38, -8], %)
|
|> line([-20.38, -8], %)
|
||||||
|> line([-15.79, 17.08], %)
|
|> line([-15.79, 17.08], %)
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
yohey([15.79, -34.6])
|
yohey([15.79, -34.6])
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
selectionsSnippets
|
selectionsSnippets
|
||||||
)
|
)
|
||||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
await page.setBodyDimensions({ width: 1200, height: 1000 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -1222,6 +1226,7 @@ extrude001 = extrude(50, sketch001)
|
|||||||
|
|
||||||
test('Deselecting line tool should mean nothing happens on click', async ({
|
test('Deselecting line tool should mean nothing happens on click', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
/**
|
/**
|
||||||
* If the line tool is clicked when the state is 'No Points' it will exit Sketch mode.
|
* If the line tool is clicked when the state is 'No Points' it will exit Sketch mode.
|
||||||
@ -1230,9 +1235,9 @@ extrude001 = extrude(50, sketch001)
|
|||||||
* To continue to test this workflow, we now enter sketch mode and place a single point before exiting the line tool.
|
* To continue to test this workflow, we now enter sketch mode and place a single point before exiting the line tool.
|
||||||
*/
|
*/
|
||||||
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 expect(
|
await expect(
|
||||||
|
|||||||
@ -1,14 +1,7 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import {
|
import { getUtils, executorInputPath, createProject } from './test-utils'
|
||||||
getUtils,
|
|
||||||
setup,
|
|
||||||
setupElectron,
|
|
||||||
tearDown,
|
|
||||||
executorInputPath,
|
|
||||||
createProject,
|
|
||||||
} from './test-utils'
|
|
||||||
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
|
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
|
||||||
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
|
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
|
||||||
import {
|
import {
|
||||||
@ -19,141 +12,136 @@ import {
|
|||||||
} from './storageStates'
|
} from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
|
||||||
await setup(context, page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Testing settings', () => {
|
test.describe('Testing settings', () => {
|
||||||
test('Stored settings are validated and fall back to defaults', async ({
|
test(
|
||||||
page,
|
'Stored settings are validated and fall back to defaults',
|
||||||
}) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
// with corrupted settings
|
// with corrupted settings
|
||||||
await page.addInitScript(
|
{
|
||||||
async ({ settingsKey, settings }) => {
|
appSettings: TEST_SETTINGS_CORRUPTED,
|
||||||
localStorage.setItem(settingsKey, settings)
|
},
|
||||||
},
|
async ({ page, homePage }) => {
|
||||||
{
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
|
||||||
settings: TOML.stringify({ settings: TEST_SETTINGS_CORRUPTED }),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
// Check the settings were reset
|
||||||
|
const storedSettings = TOML.parse(
|
||||||
|
await page.evaluate(
|
||||||
|
({ settingsKey }) => localStorage.getItem(settingsKey) || '',
|
||||||
|
{ settingsKey: TEST_SETTINGS_KEY }
|
||||||
|
)
|
||||||
|
) as { settings: SaveSettingsPayload }
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
expect(storedSettings.settings?.app?.theme).toBe('dark')
|
||||||
|
|
||||||
// Check the settings were reset
|
// Check that the invalid settings were changed to good defaults
|
||||||
const storedSettings = TOML.parse(
|
expect(storedSettings.settings?.modeling?.defaultUnit).toBe('in')
|
||||||
await page.evaluate(
|
expect(storedSettings.settings?.modeling?.mouseControls).toBe('KittyCAD')
|
||||||
({ settingsKey }) => localStorage.getItem(settingsKey) || '',
|
expect(storedSettings.settings?.app?.projectDirectory).toBe('')
|
||||||
{ settingsKey: TEST_SETTINGS_KEY }
|
expect(storedSettings.settings?.projects?.defaultProjectName).toBe(
|
||||||
|
'project-$nnn'
|
||||||
)
|
)
|
||||||
) as { settings: SaveSettingsPayload }
|
}
|
||||||
|
)
|
||||||
|
|
||||||
expect(storedSettings.settings?.app?.theme).toBe(undefined)
|
// The behavior is actually broken. Parent always takes precedence
|
||||||
|
test.fixme(
|
||||||
|
'Project settings can be set and override user settings',
|
||||||
|
async ({ page, homePage }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await test.step(`Setup`, async () => {
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.waitFor({ state: 'visible' })
|
||||||
|
})
|
||||||
|
|
||||||
// Check that the invalid settings were removed
|
// Selectors and constants
|
||||||
expect(storedSettings.settings?.modeling?.defaultUnit).toBe(undefined)
|
const paneButtonLocator = page.getByTestId('debug-pane-button')
|
||||||
expect(storedSettings.settings?.modeling?.mouseControls).toBe(undefined)
|
const headingLocator = page.getByRole('heading', {
|
||||||
expect(storedSettings.settings?.app?.projectDirectory).toBe(undefined)
|
name: 'Settings',
|
||||||
expect(storedSettings.settings?.projects?.defaultProjectName).toBe(
|
exact: true,
|
||||||
undefined
|
})
|
||||||
)
|
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
|
||||||
})
|
|
||||||
|
|
||||||
test('Project settings can be set and override user settings', async ({
|
await test.step('Open settings dialog and set "Show debug panel" to on', async () => {
|
||||||
page,
|
await page.keyboard.press('ControlOrMeta+,')
|
||||||
}) => {
|
await expect(headingLocator).toBeVisible()
|
||||||
const u = await getUtils(page)
|
|
||||||
await test.step(`Setup`, async () => {
|
/** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await test.step(`Confirm that this dialog has a solid background`, async () => {
|
||||||
await u.waitForAuthSkipAppStart()
|
await expect
|
||||||
|
.poll(
|
||||||
|
() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]),
|
||||||
|
{
|
||||||
|
timeout: 1000,
|
||||||
|
message:
|
||||||
|
'Checking for solid background, should not see default plane colors',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toBeLessThan(15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Close it and open again with keyboard shortcut, while KCL editor is focused
|
||||||
|
// Put the cursor in the editor
|
||||||
|
await test.step('Open settings with keyboard shortcut', async () => {
|
||||||
|
await page.getByTestId('settings-close-button').click()
|
||||||
|
await page.locator('.cm-content').click()
|
||||||
|
await page.keyboard.press('ControlOrMeta+,')
|
||||||
|
await expect(headingLocator).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify the toast appeared
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Set show debug panel to "false" for this project`)
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByText(`Set show debug panel to "false" for this project`)
|
||||||
|
).not.toBeVisible()
|
||||||
|
|
||||||
|
// Check that the debug panel button is gone
|
||||||
|
await expect(paneButtonLocator).not.toBeVisible()
|
||||||
|
|
||||||
|
// Check that the user setting was not changed
|
||||||
|
await page.getByRole('radio', { name: 'User' }).click()
|
||||||
|
await expect(inputLocator).toBeChecked()
|
||||||
|
|
||||||
|
// Roll back to default of "off"
|
||||||
|
await await page
|
||||||
|
.getByText(
|
||||||
|
'show debug panelRoll back show debug panelRoll back to match'
|
||||||
|
)
|
||||||
|
.hover()
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', {
|
||||||
.waitFor({ state: 'visible' })
|
name: 'Roll back show debug panel',
|
||||||
})
|
})
|
||||||
|
.click()
|
||||||
|
await expect(inputLocator).not.toBeChecked()
|
||||||
|
|
||||||
// Selectors and constants
|
// Check that the project setting did not change
|
||||||
const paneButtonLocator = page.getByTestId('debug-pane-button')
|
await page.getByRole('radio', { name: 'Project' }).click()
|
||||||
const headingLocator = page.getByRole('heading', {
|
await expect(
|
||||||
name: 'Settings',
|
page.locator('input[name="modeling-showDebugPanel"]')
|
||||||
exact: true,
|
).not.toBeChecked()
|
||||||
})
|
}
|
||||||
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
|
)
|
||||||
|
|
||||||
await test.step('Open settings dialog and set "Show debug panel" to on', async () => {
|
|
||||||
await page.keyboard.press('ControlOrMeta+Shift+,')
|
|
||||||
await expect(headingLocator).toBeVisible()
|
|
||||||
|
|
||||||
/** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */
|
|
||||||
await test.step(`Confirm that this dialog has a solid background`, async () => {
|
|
||||||
await expect
|
|
||||||
.poll(() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]), {
|
|
||||||
timeout: 1000,
|
|
||||||
message:
|
|
||||||
'Checking for solid background, should not see default plane colors',
|
|
||||||
})
|
|
||||||
.toBeLessThan(15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Close it and open again with keyboard shortcut, while KCL editor is focused
|
|
||||||
// Put the cursor in the editor
|
|
||||||
await test.step('Open settings with keyboard shortcut', async () => {
|
|
||||||
await page.getByTestId('settings-close-button').click()
|
|
||||||
await page.locator('.cm-content').click()
|
|
||||||
await page.keyboard.press('ControlOrMeta+Shift+,')
|
|
||||||
await expect(headingLocator).toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Verify the toast appeared
|
|
||||||
await expect(
|
|
||||||
page.getByText(`Set show debug panel to "false" for this project`)
|
|
||||||
).toBeVisible()
|
|
||||||
// Check that the theme changed
|
|
||||||
await expect(paneButtonLocator).not.toBeVisible()
|
|
||||||
|
|
||||||
// Check that the user setting was not changed
|
|
||||||
await page.getByRole('radio', { name: 'User' }).click()
|
|
||||||
await expect(inputLocator).toBeChecked()
|
|
||||||
|
|
||||||
// Roll back to default of "off"
|
|
||||||
await await page
|
|
||||||
.getByText('show debug panelRoll back show debug panelRoll back to match')
|
|
||||||
.hover()
|
|
||||||
await page
|
|
||||||
.getByRole('button', {
|
|
||||||
name: 'Roll back show debug panel',
|
|
||||||
})
|
|
||||||
.click()
|
|
||||||
await expect(inputLocator).not.toBeChecked()
|
|
||||||
|
|
||||||
// Check that the project setting did not change
|
|
||||||
await page.getByRole('radio', { name: 'Project' }).click()
|
|
||||||
await expect(
|
|
||||||
page.locator('input[name="modeling-showDebugPanel"]')
|
|
||||||
).not.toBeChecked()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Keybindings display the correct hotkey for Command Palette', async ({
|
test('Keybindings display the correct hotkey for Command Palette', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
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.waitForPageLoad()
|
||||||
|
|
||||||
await test.step('Open keybindings settings', async () => {
|
await test.step('Open keybindings settings', async () => {
|
||||||
// Open the settings modal with the browser keyboard shortcut
|
// Open the settings modal with the keyboard shortcut
|
||||||
await page.keyboard.press('ControlOrMeta+Shift+,')
|
await page.keyboard.press('ControlOrMeta+,')
|
||||||
|
|
||||||
// Go to Keybindings tab.
|
// Go to Keybindings tab.
|
||||||
const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' })
|
const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' })
|
||||||
@ -174,116 +162,116 @@ test.describe('Testing settings', () => {
|
|||||||
await expect(hotkey).toHaveText(text)
|
await expect(hotkey).toHaveText(text)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Project and user settings can be reset', async ({ page }) => {
|
test.fixme(
|
||||||
const u = await getUtils(page)
|
'Project and user settings can be reset',
|
||||||
await test.step(`Setup`, async () => {
|
async ({ page, homePage }) => {
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
const u = await getUtils(page)
|
||||||
await u.waitForAuthSkipAppStart()
|
await test.step(`Setup`, async () => {
|
||||||
})
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
// Selectors and constants
|
await u.waitForPageLoad()
|
||||||
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
|
await page.waitForTimeout(1000)
|
||||||
const userSettingsTab = page.getByRole('radio', { name: 'User' })
|
|
||||||
const resetButton = (level: SettingsLevel) =>
|
|
||||||
page.getByRole('button', {
|
|
||||||
name: `Reset ${level}-level settings`,
|
|
||||||
})
|
|
||||||
const themeColorSetting = page.locator('#themeColor').getByRole('slider')
|
|
||||||
const settingValues = {
|
|
||||||
default: '259',
|
|
||||||
user: '120',
|
|
||||||
project: '50',
|
|
||||||
}
|
|
||||||
const resetToast = (level: SettingsLevel) =>
|
|
||||||
page.getByText(`${level}-level settings were reset`)
|
|
||||||
|
|
||||||
await test.step(`Open the settings modal`, async () => {
|
|
||||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
|
||||||
await expect(
|
|
||||||
page.getByRole('heading', { name: 'Settings', exact: true })
|
|
||||||
).toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Set up theme color', async () => {
|
|
||||||
// Verify we're looking at the project-level settings,
|
|
||||||
// and it's set to default value
|
|
||||||
await expect(projectSettingsTab).toBeChecked()
|
|
||||||
await expect(themeColorSetting).toHaveValue(settingValues.default)
|
|
||||||
|
|
||||||
// Set project-level value to 50
|
|
||||||
await themeColorSetting.fill(settingValues.project)
|
|
||||||
|
|
||||||
// Set user-level value to 120
|
|
||||||
await userSettingsTab.click()
|
|
||||||
await themeColorSetting.fill(settingValues.user)
|
|
||||||
await projectSettingsTab.click()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Reset project settings', async () => {
|
|
||||||
// Click the reset settings button.
|
|
||||||
await resetButton('project').click()
|
|
||||||
|
|
||||||
await expect(resetToast('project')).toBeVisible()
|
|
||||||
await expect(resetToast('project')).not.toBeVisible()
|
|
||||||
|
|
||||||
// Verify it is now set to the inherited user value
|
|
||||||
await expect(themeColorSetting).toHaveValue(settingValues.user)
|
|
||||||
|
|
||||||
await test.step(`Check that the user settings did not change`, async () => {
|
|
||||||
await userSettingsTab.click()
|
|
||||||
await expect(themeColorSetting).toHaveValue(settingValues.user)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Set project-level again to test the user-level reset`, async () => {
|
// Selectors and constants
|
||||||
await projectSettingsTab.click()
|
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
|
||||||
|
const userSettingsTab = page.getByRole('radio', { name: 'User' })
|
||||||
|
const resetButton = (level: SettingsLevel) =>
|
||||||
|
page.getByRole('button', {
|
||||||
|
name: `Reset ${level}-level settings`,
|
||||||
|
})
|
||||||
|
const themeColorSetting = page.locator('#themeColor').getByRole('slider')
|
||||||
|
const settingValues = {
|
||||||
|
default: '259',
|
||||||
|
user: '120',
|
||||||
|
project: '50',
|
||||||
|
}
|
||||||
|
const resetToast = (level: SettingsLevel) =>
|
||||||
|
page.getByText(`${level}-level settings were reset`)
|
||||||
|
|
||||||
|
await test.step(`Open the settings modal`, async () => {
|
||||||
|
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Set up theme color', async () => {
|
||||||
|
// Verify we're looking at the project-level settings,
|
||||||
|
// and it's set to default value
|
||||||
|
await expect(projectSettingsTab).toBeChecked()
|
||||||
|
await expect(themeColorSetting).toHaveValue(settingValues.default)
|
||||||
|
|
||||||
|
// Set project-level value to 50
|
||||||
await themeColorSetting.fill(settingValues.project)
|
await themeColorSetting.fill(settingValues.project)
|
||||||
|
|
||||||
|
// Set user-level value to 120
|
||||||
await userSettingsTab.click()
|
await userSettingsTab.click()
|
||||||
})
|
await themeColorSetting.fill(settingValues.user)
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Reset user settings', async () => {
|
|
||||||
// Click the reset settings button.
|
|
||||||
await resetButton('user').click()
|
|
||||||
|
|
||||||
await expect(resetToast('user')).toBeVisible()
|
|
||||||
await expect(resetToast('user')).not.toBeVisible()
|
|
||||||
|
|
||||||
// Verify it is now set to the default value
|
|
||||||
await expect(themeColorSetting).toHaveValue(settingValues.default)
|
|
||||||
|
|
||||||
await test.step(`Check that the project settings did not change`, async () => {
|
|
||||||
await projectSettingsTab.click()
|
await projectSettingsTab.click()
|
||||||
await expect(themeColorSetting).toHaveValue(settingValues.project)
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
})
|
await test.step('Reset project settings', async () => {
|
||||||
|
// Click the reset settings button.
|
||||||
|
await resetButton('project').click()
|
||||||
|
|
||||||
|
await expect(resetToast('project')).toBeVisible()
|
||||||
|
await expect(resetToast('project')).not.toBeVisible()
|
||||||
|
|
||||||
|
// Verify it is now set to the inherited user value
|
||||||
|
await expect(themeColorSetting).toHaveValue(settingValues.user)
|
||||||
|
|
||||||
|
await test.step(`Check that the user settings did not change`, async () => {
|
||||||
|
await userSettingsTab.click()
|
||||||
|
await expect(themeColorSetting).toHaveValue(settingValues.user)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Set project-level again to test the user-level reset`, async () => {
|
||||||
|
await projectSettingsTab.click()
|
||||||
|
await themeColorSetting.fill(settingValues.project)
|
||||||
|
await userSettingsTab.click()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Reset user settings', async () => {
|
||||||
|
// Click the reset settings button.
|
||||||
|
await resetButton('user').click()
|
||||||
|
|
||||||
|
await expect(resetToast('user')).toBeVisible()
|
||||||
|
await expect(resetToast('user')).not.toBeVisible()
|
||||||
|
|
||||||
|
// Verify it is now set to the default value
|
||||||
|
await expect(themeColorSetting).toHaveValue(settingValues.default)
|
||||||
|
|
||||||
|
await test.step(`Check that the project settings did not change`, async () => {
|
||||||
|
await projectSettingsTab.click()
|
||||||
|
await expect(themeColorSetting).toHaveValue(settingValues.project)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test.fixme(
|
test.fixme(
|
||||||
`Project settings override user settings on desktop`,
|
`Project settings override user settings on desktop`,
|
||||||
{ tag: ['@electron', '@skipWin'] },
|
{ tag: ['@electron', '@skipWin'] },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
test.skip(
|
test.skip(
|
||||||
process.platform === 'win32',
|
process.platform === 'win32',
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||||
)
|
)
|
||||||
const projectName = 'bracket'
|
const projectName = 'bracket'
|
||||||
const {
|
const { dir: projectDirName } = await context.folderSetupFn(
|
||||||
electronApp,
|
async (dir) => {
|
||||||
page,
|
|
||||||
dir: projectDirName,
|
|
||||||
} = await setupElectron({
|
|
||||||
testInfo,
|
|
||||||
folderSetupFn: async (dir) => {
|
|
||||||
const bracketDir = join(dir, projectName)
|
const bracketDir = join(dir, projectName)
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
join(bracketDir, 'main.kcl')
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
// Selectors and constants
|
// Selectors and constants
|
||||||
const tempProjectSettingsFilePath = join(
|
const tempProjectSettingsFilePath = join(
|
||||||
@ -353,22 +341,18 @@ test.describe('Testing settings', () => {
|
|||||||
await logoLink.click()
|
await logoLink.click()
|
||||||
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
|
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
`Load desktop app with no settings file`,
|
`Load desktop app with no settings file`,
|
||||||
{ tag: '@electron' },
|
{
|
||||||
async ({ browser: _ }, testInfo) => {
|
tag: '@electron',
|
||||||
const { electronApp, page } = await setupElectron({
|
// This is what makes no settings file get created
|
||||||
// This is what makes no settings file get created
|
cleanProjectDir: false,
|
||||||
cleanProjectDir: false,
|
},
|
||||||
testInfo,
|
async ({ page }, testInfo) => {
|
||||||
})
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
// Selectors and constants
|
// Selectors and constants
|
||||||
const errorHeading = page.getByRole('heading', {
|
const errorHeading = page.getByRole('heading', {
|
||||||
@ -379,25 +363,21 @@ test.describe('Testing settings', () => {
|
|||||||
// If the app loads without exploding we're in the clear
|
// If the app loads without exploding we're in the clear
|
||||||
await expect(errorHeading).not.toBeVisible()
|
await expect(errorHeading).not.toBeVisible()
|
||||||
await expect(projectDirLink).toBeVisible()
|
await expect(projectDirLink).toBeVisible()
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
`Load desktop app with a settings file, but no project directory setting`,
|
`Load desktop app with a settings file, but no project directory setting`,
|
||||||
{ tag: '@electron' },
|
{
|
||||||
async ({ browser: _ }, testInfo) => {
|
tag: '@electron',
|
||||||
const { electronApp, page } = await setupElectron({
|
appSettings: {
|
||||||
testInfo,
|
app: {
|
||||||
appSettings: {
|
themeColor: '259',
|
||||||
app: {
|
|
||||||
themeColor: '259',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
|
},
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
async ({ context, page }, testInfo) => {
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
// Selectors and constants
|
// Selectors and constants
|
||||||
const errorHeading = page.getByRole('heading', {
|
const errorHeading = page.getByRole('heading', {
|
||||||
@ -408,32 +388,28 @@ test.describe('Testing settings', () => {
|
|||||||
// If the app loads without exploding we're in the clear
|
// If the app loads without exploding we're in the clear
|
||||||
await expect(errorHeading).not.toBeVisible()
|
await expect(errorHeading).not.toBeVisible()
|
||||||
await expect(projectDirLink).toBeVisible()
|
await expect(projectDirLink).toBeVisible()
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// It was much easier to test the logo color than the background stream color.
|
// It was much easier to test the logo color than the background stream color.
|
||||||
test.fixme(
|
test.fixme(
|
||||||
'user settings reload on external change, on project and modeling view',
|
'user settings reload on external change, on project and modeling view',
|
||||||
{ tag: '@electron' },
|
{
|
||||||
async ({ browserName }, testInfo) => {
|
tag: '@electron',
|
||||||
const {
|
appSettings: {
|
||||||
electronApp,
|
app: {
|
||||||
page,
|
// Doesn't matter what you set it to. It will
|
||||||
dir: projectDirName,
|
// default to 264.5
|
||||||
} = await setupElectron({
|
themeColor: '0',
|
||||||
testInfo,
|
|
||||||
appSettings: {
|
|
||||||
app: {
|
|
||||||
// Doesn't matter what you set it to. It will
|
|
||||||
// default to 264.5
|
|
||||||
themeColor: '0',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
|
},
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
const { dir: projectDirName } = await context.folderSetupFn(
|
||||||
|
async () => {}
|
||||||
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
const logoLink = page.getByTestId('app-logo')
|
const logoLink = page.getByTestId('app-logo')
|
||||||
const projectDirLink = page.getByText('Loaded from')
|
const projectDirLink = page.getByText('Loaded from')
|
||||||
@ -467,23 +443,18 @@ test.describe('Testing settings', () => {
|
|||||||
await changeColor('21')
|
await changeColor('21')
|
||||||
await expect(logoLink).toHaveCSS('--primary-hue', '21')
|
await expect(logoLink).toHaveCSS('--primary-hue', '21')
|
||||||
})
|
})
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test.fixme(
|
||||||
'project settings reload on external change',
|
'project settings reload on external change',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName: _ }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const {
|
const { dir: projectDirName } = await context.folderSetupFn(
|
||||||
electronApp,
|
async () => {}
|
||||||
page,
|
)
|
||||||
dir: projectDirName,
|
|
||||||
} = await setupElectron({
|
|
||||||
testInfo,
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
const logoLink = page.getByTestId('app-logo')
|
const logoLink = page.getByTestId('app-logo')
|
||||||
const projectDirLink = page.getByText('Loaded from')
|
const projectDirLink = page.getByText('Loaded from')
|
||||||
@ -514,29 +485,24 @@ test.describe('Testing settings', () => {
|
|||||||
await changeColorFs('99')
|
await changeColorFs('99')
|
||||||
await expect(logoLink).toHaveCSS('--primary-hue', '99')
|
await expect(logoLink).toHaveCSS('--primary-hue', '99')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
`Closing settings modal should go back to the original file being viewed`,
|
`Closing settings modal should go back to the original file being viewed`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { electronApp, page } = await setupElectron({
|
await context.folderSetupFn(async (dir) => {
|
||||||
testInfo,
|
const bracketDir = join(dir, 'project-000')
|
||||||
folderSetupFn: async (dir) => {
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
const bracketDir = join(dir, 'project-000')
|
await fsp.copyFile(
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
executorInputPath('cube.kcl'),
|
||||||
await fsp.copyFile(
|
join(bracketDir, 'main.kcl')
|
||||||
executorInputPath('cube.kcl'),
|
)
|
||||||
join(bracketDir, 'main.kcl')
|
await fsp.copyFile(
|
||||||
)
|
executorInputPath('cylinder.kcl'),
|
||||||
await fsp.copyFile(
|
join(bracketDir, '2.kcl')
|
||||||
executorInputPath('cylinder.kcl'),
|
)
|
||||||
join(bracketDir, '2.kcl')
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8')
|
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8')
|
||||||
const kclCylinder = await fsp.readFile(
|
const kclCylinder = await fsp.readFile(
|
||||||
@ -552,7 +518,7 @@ test.describe('Testing settings', () => {
|
|||||||
editorTextMatches,
|
editorTextMatches,
|
||||||
} = await getUtils(page, test)
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
await test.step('Precondition: Open to second project file', async () => {
|
await test.step('Precondition: Open to second project file', async () => {
|
||||||
@ -583,16 +549,15 @@ test.describe('Testing settings', () => {
|
|||||||
await test.step('Postcondition: Same file content is in editor as before settings opened', async () => {
|
await test.step('Postcondition: Same file content is in editor as before settings opened', async () => {
|
||||||
await editorTextMatches(kclCylinder)
|
await editorTextMatches(kclCylinder)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test('Changing modeling default unit', async ({ page }) => {
|
test('Changing modeling default unit', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
|
||||||
await test.step(`Test setup`, async () => {
|
await test.step(`Test setup`, async () => {
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
const toastMessage = page.getByText(`Successfully created "testDefault"`)
|
||||||
|
await expect(toastMessage).not.toBeVisible()
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.waitFor({ state: 'visible' })
|
.waitFor({ state: 'visible' })
|
||||||
@ -619,7 +584,9 @@ test.describe('Testing settings', () => {
|
|||||||
await userSettingsTab.click()
|
await userSettingsTab.click()
|
||||||
await defaultUnitSection.hover()
|
await defaultUnitSection.hover()
|
||||||
await defaultUnitRollbackButton.click()
|
await defaultUnitRollbackButton.click()
|
||||||
|
await projectSettingsTab.hover()
|
||||||
await projectSettingsTab.click()
|
await projectSettingsTab.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Change modeling default unit within project tab', async () => {
|
await test.step('Change modeling default unit within project tab', async () => {
|
||||||
@ -631,7 +598,10 @@ test.describe('Testing settings', () => {
|
|||||||
const toastMessage = page.getByText(
|
const toastMessage = page.getByText(
|
||||||
`Set default unit to "${unitOfMeasure}" for this project`
|
`Set default unit to "${unitOfMeasure}" for this project`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Assert visibility and disapperance
|
||||||
await expect(toastMessage).toBeVisible()
|
await expect(toastMessage).toBeVisible()
|
||||||
|
await expect(toastMessage).not.toBeVisible()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await changeUnitOfMeasureInProjectTab('in')
|
await changeUnitOfMeasureInProjectTab('in')
|
||||||
@ -643,7 +613,10 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Go to the user tab
|
// Go to the user tab
|
||||||
|
await userSettingsTab.hover()
|
||||||
await userSettingsTab.click()
|
await userSettingsTab.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
await test.step('Change modeling default unit within user tab', async () => {
|
await test.step('Change modeling default unit within user tab', async () => {
|
||||||
const changeUnitOfMeasureInUserTab = async (unitOfMeasure: string) => {
|
const changeUnitOfMeasureInUserTab = async (unitOfMeasure: string) => {
|
||||||
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
|
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
|
||||||
@ -726,23 +699,26 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Changing theme in sketch mode', async ({ page }) => {
|
test('Changing theme in sketch mode', async ({ context, page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(() => {
|
await context.addInitScript(() => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([5, 0], %)
|
|> line([5, 0], %)
|
||||||
|> line([0, 5], %)
|
|> line([0, 5], %)
|
||||||
|> line([-5, 0], %)
|
|> line([-5, 0], %)
|
||||||
|> 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 homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
// Selectors and constants
|
// Selectors and constants
|
||||||
const editSketchButton = page.getByRole('button', { name: 'Edit Sketch' })
|
const editSketchButton = page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
@ -753,7 +729,6 @@ extrude001 = extrude(5, sketch001)
|
|||||||
const lightThemeSegmentColor: [number, number, number] = [90, 90, 90]
|
const lightThemeSegmentColor: [number, number, number] = [90, 90, 90]
|
||||||
|
|
||||||
await test.step(`Get into sketch mode`, async () => {
|
await test.step(`Get into sketch mode`, async () => {
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
await expect(editSketchButton).toBeVisible()
|
await expect(editSketchButton).toBeVisible()
|
||||||
await editSketchButton.click()
|
await editSketchButton.click()
|
||||||
@ -792,135 +767,125 @@ extrude001 = extrude(5, sketch001)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`Changing system theme preferences (via media query) should update UI and stream`, async ({
|
test(
|
||||||
page,
|
`Changing system theme preferences (via media query) should update UI and stream`,
|
||||||
}) => {
|
{
|
||||||
// Override the settings so that the theme is set to `system`
|
// Override the settings so that the theme is set to `system`
|
||||||
await page.addInitScript(
|
appSettings: TEST_SETTINGS_DEFAULT_THEME,
|
||||||
({ settingsKey, settings }) => {
|
},
|
||||||
localStorage.setItem(settingsKey, settings)
|
async ({ page, homePage }) => {
|
||||||
},
|
const u = await getUtils(page)
|
||||||
{
|
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
// Selectors and constants
|
||||||
settings: TOML.stringify({
|
const darkBackgroundCss = 'oklch(0.3012 0 264.5)'
|
||||||
settings: TEST_SETTINGS_DEFAULT_THEME,
|
const lightBackgroundCss = 'oklch(0.9911 0 264.5)'
|
||||||
}),
|
const darkBackgroundColor: [number, number, number] = [27, 27, 27]
|
||||||
|
const lightBackgroundColor: [number, number, number] = [245, 245, 245]
|
||||||
|
const streamBackgroundPixelIsColor = async (
|
||||||
|
color: [number, number, number]
|
||||||
|
) => {
|
||||||
|
return u.getGreatestPixDiff({ x: 1000, y: 200 }, color)
|
||||||
}
|
}
|
||||||
)
|
const toolbar = page.locator('menu').filter({ hasText: 'Start Sketch' })
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
// Selectors and constants
|
await test.step(`Test setup`, async () => {
|
||||||
const darkBackgroundCss = 'oklch(0.3012 0 264.5)'
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
const lightBackgroundCss = 'oklch(0.9911 0 264.5)'
|
await homePage.goToModelingScene()
|
||||||
const darkBackgroundColor: [number, number, number] = [27, 27, 27]
|
await u.waitForPageLoad()
|
||||||
const lightBackgroundColor: [number, number, number] = [245, 245, 245]
|
await page.waitForTimeout(1000)
|
||||||
const streamBackgroundPixelIsColor = async (
|
await expect(toolbar).toBeVisible()
|
||||||
color: [number, number, number]
|
})
|
||||||
) => {
|
|
||||||
return u.getGreatestPixDiff({ x: 1000, y: 200 }, color)
|
await test.step(`Check the background color is light before`, async () => {
|
||||||
|
await expect(toolbar).toHaveCSS('background-color', lightBackgroundCss)
|
||||||
|
await expect
|
||||||
|
.poll(() => streamBackgroundPixelIsColor(lightBackgroundColor))
|
||||||
|
.toBeLessThan(15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Change media query preference to dark, emulating dusk with system theme`, async () => {
|
||||||
|
await page.emulateMedia({ colorScheme: 'dark' })
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Check the background color is dark after`, async () => {
|
||||||
|
await expect(toolbar).toHaveCSS('background-color', darkBackgroundCss)
|
||||||
|
await expect
|
||||||
|
.poll(() => streamBackgroundPixelIsColor(darkBackgroundColor))
|
||||||
|
.toBeLessThan(15)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const toolbar = page.locator('menu').filter({ hasText: 'Start Sketch' })
|
)
|
||||||
|
|
||||||
await test.step(`Test setup`, async () => {
|
test(
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
`Turning off "Show debug panel" with debug panel open leaves no phantom panel`,
|
||||||
await u.waitForAuthSkipAppStart()
|
{
|
||||||
await expect(toolbar).toBeVisible()
|
// Override beforeEach test setup
|
||||||
})
|
// with debug panel open
|
||||||
|
// but "show debug panel" set to false
|
||||||
|
appSettings: {
|
||||||
|
...TEST_SETTINGS,
|
||||||
|
modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async ({ context, page, homePage }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await test.step(`Check the background color is light before`, async () => {
|
await context.addInitScript(async () => {
|
||||||
await expect(toolbar).toHaveCSS('background-color', lightBackgroundCss)
|
|
||||||
await expect
|
|
||||||
.poll(() => streamBackgroundPixelIsColor(lightBackgroundColor))
|
|
||||||
.toBeLessThan(15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Change media query preference to dark, emulating dusk with system theme`, async () => {
|
|
||||||
await page.emulateMedia({ colorScheme: 'dark' })
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Check the background color is dark after`, async () => {
|
|
||||||
await expect(toolbar).toHaveCSS('background-color', darkBackgroundCss)
|
|
||||||
await expect
|
|
||||||
.poll(() => streamBackgroundPixelIsColor(darkBackgroundColor))
|
|
||||||
.toBeLessThan(15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
// Override beforeEach test setup
|
|
||||||
// with debug panel open
|
|
||||||
// but "show debug panel" set to false
|
|
||||||
await page.addInitScript(
|
|
||||||
async ({ settingsKey, settings }) => {
|
|
||||||
localStorage.setItem(settingsKey, settings)
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistModelingContext',
|
'persistModelingContext',
|
||||||
'{"openPanes":["debug"]}'
|
'{"openPanes":["debug"]}'
|
||||||
)
|
)
|
||||||
},
|
})
|
||||||
{
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
await homePage.goToModelingScene()
|
||||||
settings: TOML.stringify({
|
|
||||||
settings: {
|
// Constants and locators
|
||||||
...TEST_SETTINGS,
|
const resizeHandle = page.locator('.sidebar-resize-handles > div.block')
|
||||||
modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false },
|
const debugPaneButton = page.getByTestId('debug-pane-button')
|
||||||
},
|
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
||||||
}),
|
const debugPaneOption = page.getByRole('option', {
|
||||||
|
name: 'Settings · modeling · show debug panel',
|
||||||
|
})
|
||||||
|
|
||||||
|
async function setShowDebugPanelTo(value: 'On' | 'Off') {
|
||||||
|
await commandsButton.click()
|
||||||
|
await debugPaneOption.click()
|
||||||
|
await page.getByRole('option', { name: value }).click()
|
||||||
|
await expect(
|
||||||
|
page.getByText(
|
||||||
|
`Set show debug panel to "${value === 'On'}" for this project`
|
||||||
|
)
|
||||||
|
).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
// Constants and locators
|
await test.step(`Initial load with corrupted settings`, async () => {
|
||||||
const resizeHandle = page.locator('.sidebar-resize-handles > div.block')
|
// Check that the debug panel is not visible
|
||||||
const debugPaneButton = page.getByTestId('debug-pane-button')
|
await expect(debugPaneButton).not.toBeVisible()
|
||||||
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
// Check the pane resize handle wrapper is not visible
|
||||||
const debugPaneOption = page.getByRole('option', {
|
await expect(resizeHandle).not.toBeVisible()
|
||||||
name: 'Settings · modeling · show debug panel',
|
})
|
||||||
})
|
|
||||||
|
|
||||||
async function setShowDebugPanelTo(value: 'On' | 'Off') {
|
await test.step(`Open code pane to verify we see the resize handles`, async () => {
|
||||||
await commandsButton.click()
|
await u.openKclCodePanel()
|
||||||
await debugPaneOption.click()
|
await expect(resizeHandle).toBeVisible()
|
||||||
await page.getByRole('option', { name: value }).click()
|
await u.closeKclCodePanel()
|
||||||
await expect(
|
})
|
||||||
page.getByText(
|
|
||||||
`Set show debug panel to "${value === 'On'}" for this project`
|
await test.step(`Turn on debug panel, open it`, async () => {
|
||||||
)
|
await setShowDebugPanelTo('On')
|
||||||
).toBeVisible()
|
await expect(debugPaneButton).toBeVisible()
|
||||||
|
// We want the logic to clear the phantom panel, so we shouldn't see
|
||||||
|
// the real panel (and therefore the resize handle) yet
|
||||||
|
await expect(resizeHandle).not.toBeVisible()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await expect(resizeHandle).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Turn off debug panel setting with it open`, async () => {
|
||||||
|
await setShowDebugPanelTo('Off')
|
||||||
|
await expect(debugPaneButton).not.toBeVisible()
|
||||||
|
await expect(resizeHandle).not.toBeVisible()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
)
|
||||||
await test.step(`Initial load with corrupted settings`, async () => {
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
// Check that the debug panel is not visible
|
|
||||||
await expect(debugPaneButton).not.toBeVisible()
|
|
||||||
// Check the pane resize handle wrapper is not visible
|
|
||||||
await expect(resizeHandle).not.toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Open code pane to verify we see the resize handles`, async () => {
|
|
||||||
await u.openKclCodePanel()
|
|
||||||
await expect(resizeHandle).toBeVisible()
|
|
||||||
await u.closeKclCodePanel()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Turn on debug panel, open it`, async () => {
|
|
||||||
await setShowDebugPanelTo('On')
|
|
||||||
await expect(debugPaneButton).toBeVisible()
|
|
||||||
// We want the logic to clear the phantom panel, so we shouldn't see
|
|
||||||
// the real panel (and therefore the resize handle) yet
|
|
||||||
await expect(resizeHandle).not.toBeVisible()
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await expect(resizeHandle).toBeVisible()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Turn off debug panel setting with it open`, async () => {
|
|
||||||
await setShowDebugPanelTo('Off')
|
|
||||||
await expect(debugPaneButton).not.toBeVisible()
|
|
||||||
await expect(resizeHandle).not.toBeVisible()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,29 +1,16 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page } from './zoo-test'
|
||||||
import {
|
import { getUtils, createProject } from './test-utils'
|
||||||
getUtils,
|
|
||||||
setup,
|
|
||||||
tearDown,
|
|
||||||
setupElectron,
|
|
||||||
createProject,
|
|
||||||
} from './test-utils'
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
|
||||||
await setup(context, page)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Text-to-CAD tests', () => {
|
test.describe('Text-to-CAD tests', () => {
|
||||||
test('basic lego happy case', async ({ page }) => {
|
test('basic lego happy case', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await test.step('Set up', async () => {
|
await test.step('Set up', async () => {
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||||
@ -43,25 +30,17 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
// Hit accept.
|
||||||
|
|
||||||
// Hit copy to clipboard.
|
|
||||||
const copyToClipboardButton = page.getByRole('button', {
|
const copyToClipboardButton = page.getByRole('button', {
|
||||||
name: 'Copy to clipboard',
|
name: 'Accept',
|
||||||
})
|
})
|
||||||
await expect(copyToClipboardButton).toBeVisible()
|
await expect(copyToClipboardButton).toBeVisible()
|
||||||
|
|
||||||
await copyToClipboardButton.click()
|
await copyToClipboardButton.click()
|
||||||
|
|
||||||
// Expect the code to be copied.
|
|
||||||
await expect(page.getByText('Copied')).toBeVisible()
|
|
||||||
|
|
||||||
// Click in the code editor.
|
// Click in the code editor.
|
||||||
await page.locator('.cm-content').click()
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
// Paste the code.
|
|
||||||
await page.keyboard.press('ControlOrMeta+KeyV')
|
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
// Expect the code to be pasted.
|
||||||
await expect(page.locator('.cm-content')).toContainText(`const`)
|
await expect(page.locator('.cm-content')).toContainText(`const`)
|
||||||
|
|
||||||
@ -70,29 +49,18 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
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()
|
||||||
|
|
||||||
// Find the toast close button.
|
|
||||||
const closeButton = page
|
|
||||||
.getByRole('status')
|
|
||||||
.locator('div')
|
|
||||||
.filter({ hasText: 'Text-to-CAD successfulPrompt' })
|
|
||||||
.first()
|
|
||||||
.getByRole('button', { name: 'Close' })
|
|
||||||
await expect(closeButton).toBeVisible()
|
|
||||||
await closeButton.click()
|
|
||||||
|
|
||||||
// The toast should disappear.
|
|
||||||
await expect(successToastMessage).not.toBeVisible()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
|
test('success model, then ignore success toast, user can create new prompt from command bar', 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()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x6 lego')
|
await sendPromptFromCommandBar(page, 'a 2x6 lego')
|
||||||
|
|
||||||
@ -111,10 +79,6 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
|
||||||
|
|
||||||
await expect(successToastMessage).toBeVisible()
|
|
||||||
|
|
||||||
// Can send a new prompt from the command bar.
|
// Can send a new prompt from the command bar.
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||||
|
|
||||||
@ -133,12 +97,14 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
test('you can reject text-to-cad output and it does nothing', async ({
|
test('you can reject text-to-cad output and it does nothing', 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()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||||
|
|
||||||
@ -170,12 +136,16 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(page.locator('.cm-content')).toContainText(``)
|
await expect(page.locator('.cm-content')).toContainText(``)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('sending a bad prompt fails, can dismiss', async ({ page }) => {
|
test('sending a bad prompt fails, can dismiss', 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 commandBarButton = page.getByRole('button', { name: 'Commands' })
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
await expect(commandBarButton).toBeVisible()
|
await expect(commandBarButton).toBeVisible()
|
||||||
@ -236,12 +206,14 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
test('sending a bad prompt fails, can start over from toast', async ({
|
test('sending a bad prompt fails, can start over from toast', 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()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
await expect(commandBarButton).toBeVisible()
|
await expect(commandBarButton).toBeVisible()
|
||||||
@ -324,12 +296,14 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
test('sending a bad prompt fails, can ignore toast, can start over from command bar', 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()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
await expect(commandBarButton).toBeVisible()
|
await expect(commandBarButton).toBeVisible()
|
||||||
@ -391,19 +365,21 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
|
||||||
|
|
||||||
// old failure toast should stick around.
|
// old failure toast should stick around.
|
||||||
await expect(failureToastMessage).toBeVisible()
|
await expect(failureToastMessage).toBeVisible()
|
||||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('ensure you can shift+enter in the prompt box', async ({ page }) => {
|
test('ensure you can shift+enter in the prompt box', 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 promptWithNewline = `a 2x4\nlego`
|
const promptWithNewline = `a 2x4\nlego`
|
||||||
|
|
||||||
@ -456,7 +432,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test(
|
test(
|
||||||
'can do many at once and get many prompts back, and interact with many',
|
'can do many at once and get many prompts back, and interact with many',
|
||||||
{ tag: ['@skipWin'] },
|
{ tag: ['@skipWin'] },
|
||||||
async ({ page }) => {
|
async ({ page, homePage }) => {
|
||||||
// Let this test run longer since we've seen it timeout.
|
// Let this test run longer since we've seen it timeout.
|
||||||
test.setTimeout(180_000)
|
test.setTimeout(180_000)
|
||||||
// skip on windows
|
// skip on windows
|
||||||
@ -467,9 +443,10 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
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()
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||||
|
|
||||||
@ -495,8 +472,6 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
// We should have three success toasts.
|
// We should have three success toasts.
|
||||||
await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 })
|
await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 })
|
||||||
|
|
||||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
|
||||||
|
|
||||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||||
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
|
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
|
||||||
await expect(page.getByText(`a 2x10 lego`)).toBeVisible()
|
await expect(page.getByText(`a 2x10 lego`)).toBeVisible()
|
||||||
@ -514,31 +489,15 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
// Ensure you can copy the code for one of the models remaining.
|
// Ensure you can copy the code for one of the models remaining.
|
||||||
const copyToClipboardButton = page.getByRole('button', {
|
const copyToClipboardButton = page.getByRole('button', {
|
||||||
name: 'Copy to clipboard',
|
name: 'Accept',
|
||||||
})
|
})
|
||||||
await expect(copyToClipboardButton.first()).toBeVisible()
|
await expect(copyToClipboardButton.first()).toBeVisible()
|
||||||
// Click the button.
|
// Click the button.
|
||||||
await copyToClipboardButton.first().click()
|
await copyToClipboardButton.first().click()
|
||||||
|
|
||||||
// Expect the code to be copied.
|
|
||||||
await expect(page.getByText('Copied')).toBeVisible()
|
|
||||||
|
|
||||||
// Click in the code editor.
|
|
||||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
|
||||||
|
|
||||||
// Paste the code.
|
|
||||||
await page.keyboard.down('ControlOrMeta')
|
|
||||||
await page.keyboard.press('KeyV')
|
|
||||||
await page.keyboard.up('ControlOrMeta')
|
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
// Expect the code to be pasted.
|
||||||
await expect(page.locator('.cm-content')).toContainText(`2x8`)
|
await expect(page.locator('.cm-content')).toContainText(`2x8`)
|
||||||
|
|
||||||
// Find the toast close button.
|
|
||||||
const closeButton = page.locator('[data-negative-button="close"]').first()
|
|
||||||
await expect(closeButton).toBeVisible()
|
|
||||||
await closeButton.click()
|
|
||||||
|
|
||||||
// Ensure the final toast remains.
|
// Ensure the final toast remains.
|
||||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||||
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
|
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
|
||||||
@ -549,40 +508,21 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
// Click the button.
|
// Click the button.
|
||||||
await copyToClipboardButton.click()
|
await copyToClipboardButton.click()
|
||||||
|
|
||||||
// Expect the code to be copied.
|
|
||||||
await expect(page.getByText('Copied')).toBeVisible()
|
|
||||||
|
|
||||||
// Click in the code editor.
|
|
||||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
|
||||||
|
|
||||||
// Paste the code.
|
|
||||||
await page.keyboard.down('ControlOrMeta')
|
|
||||||
await page.keyboard.press('KeyA')
|
|
||||||
await page.keyboard.up('ControlOrMeta')
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await page.keyboard.down('ControlOrMeta')
|
|
||||||
await page.keyboard.press('KeyV')
|
|
||||||
await page.keyboard.up('ControlOrMeta')
|
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
// Expect the code to be pasted.
|
||||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||||
|
|
||||||
// Expect the toast to disappear.
|
|
||||||
// Find the toast close button.
|
|
||||||
await expect(closeButton).toBeVisible()
|
|
||||||
await closeButton.click()
|
|
||||||
await expect(successToastMessage).not.toBeVisible()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
|
test('can do many at once with errors, clicking dismiss error does not dismiss all', 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()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||||
|
|
||||||
@ -631,57 +571,37 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
// Ensure you can copy the code for one of the models remaining.
|
// Ensure you can copy the code for one of the models remaining.
|
||||||
const copyToClipboardButton = page.getByRole('button', {
|
const copyToClipboardButton = page.getByRole('button', {
|
||||||
name: 'Copy to clipboard',
|
name: 'Accept',
|
||||||
})
|
})
|
||||||
await expect(copyToClipboardButton.first()).toBeVisible()
|
await expect(copyToClipboardButton.first()).toBeVisible()
|
||||||
// Click the button.
|
// Click the button.
|
||||||
await copyToClipboardButton.first().click()
|
await copyToClipboardButton.first().click()
|
||||||
|
|
||||||
// Expect the code to be copied.
|
|
||||||
await expect(page.getByText('Copied')).toBeVisible()
|
|
||||||
|
|
||||||
// Click in the code editor.
|
|
||||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
|
||||||
|
|
||||||
// Paste the code.
|
|
||||||
await page.keyboard.down('ControlOrMeta')
|
|
||||||
await page.keyboard.press('KeyV')
|
|
||||||
await page.keyboard.up('ControlOrMeta')
|
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
// Expect the code to be pasted.
|
||||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||||
|
|
||||||
// Find the toast close button.
|
|
||||||
const closeButton = page
|
|
||||||
.getByRole('status')
|
|
||||||
.locator('div')
|
|
||||||
.filter({ hasText: 'Text-to-CAD successfulPrompt' })
|
|
||||||
.first()
|
|
||||||
.getByRole('button', { name: 'Close' })
|
|
||||||
await expect(closeButton).toBeVisible()
|
|
||||||
await closeButton.click()
|
|
||||||
|
|
||||||
// Expect the toast to disappear.
|
|
||||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
|
||||||
await expect(successToastMessage).not.toBeVisible()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
await expect(commandBarButton).toBeVisible()
|
await expect(commandBarButton).toBeVisible()
|
||||||
// Click the command bar button
|
// Click the command bar button
|
||||||
|
await commandBarButton.hover()
|
||||||
await commandBarButton.click()
|
await commandBarButton.click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
// Wait for the command bar to appear
|
// Wait for the command bar to appear
|
||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API ')
|
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API')
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
|
await textToCadCommand.first().scrollIntoViewIfNeeded()
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByText('Prompt')
|
const prompt = page.getByText('Prompt')
|
||||||
@ -697,12 +617,13 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
test(
|
test(
|
||||||
'Text-to-CAD functionality',
|
'Text-to-CAD functionality',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const projectName = 'project-000'
|
const projectName = 'project-000'
|
||||||
const prompt = 'lego 2x4'
|
const prompt = 'lego 2x4'
|
||||||
const textToCadFileName = 'lego-2x4.kcl'
|
const textToCadFileName = 'lego-2x4.kcl'
|
||||||
|
|
||||||
const { electronApp, page, dir } = await setupElectron({ testInfo })
|
const { dir } = await context.folderSetupFn(async () => {})
|
||||||
|
|
||||||
const fileExists = () =>
|
const fileExists = () =>
|
||||||
fs.existsSync(join(dir, projectName, textToCadFileName))
|
fs.existsSync(join(dir, projectName, textToCadFileName))
|
||||||
|
|
||||||
@ -711,7 +632,7 @@ test(
|
|||||||
test
|
test
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
// Locators
|
// Locators
|
||||||
const projectMenuButton = page
|
const projectMenuButton = page
|
||||||
@ -761,7 +682,5 @@ test(
|
|||||||
// Confirm we've navigated back to the main.kcl file after deletion
|
// Confirm we've navigated back to the main.kcl file after deletion
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,19 +1,10 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { doExport, getUtils, makeTemplate, setup, tearDown } from './test-utils'
|
import { doExport, getUtils, makeTemplate } from './test-utils'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
test.fixme('Units menu', async ({ page, homePage }) => {
|
||||||
await setup(context, page, testInfo)
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
})
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
|
||||||
await tearDown(page, testInfo)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Units menu', async ({ page }) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
const unitsMenuButton = page.getByRole('button', {
|
const unitsMenuButton = page.getByRole('button', {
|
||||||
name: 'Current Units',
|
name: 'Current Units',
|
||||||
@ -41,7 +32,7 @@ test('Units menu', async ({ page }) => {
|
|||||||
await expect(unitsMenuButton).toContainText('mm')
|
await expect(unitsMenuButton).toContainText('mm')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Successful export shows a success toast', async ({ page }) => {
|
test('Successful export shows a success toast', async ({ page, homePage }) => {
|
||||||
// FYI this test doesn't work with only engine running locally
|
// FYI this test doesn't work with only engine running locally
|
||||||
// And you will need to have the KittyCAD CLI installed
|
// And you will need to have the KittyCAD CLI installed
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -57,41 +48,41 @@ totalHeightHalf = 2
|
|||||||
armThick = 0.5
|
armThick = 0.5
|
||||||
totalLen = 9.5
|
totalLen = 9.5
|
||||||
part001 = startSketchOn('-XZ')
|
part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> yLine(baseHeight, %)
|
|> yLine(baseHeight, %)
|
||||||
|> xLine(baseLen, %)
|
|> xLine(baseLen, %)
|
||||||
|> angledLineToY({
|
|> angledLineToY({
|
||||||
angle: topAng,
|
angle = topAng,
|
||||||
to: totalHeightHalf,
|
to = totalHeightHalf,
|
||||||
}, %, $seg04)
|
}, %, $seg04)
|
||||||
|> xLineTo(totalLen, %, $seg03)
|
|> xLineTo(totalLen, %, $seg03)
|
||||||
|> yLine(-armThick, %, $seg01)
|
|> yLine(-armThick, %, $seg01)
|
||||||
|> angledLineThatIntersects({
|
|> angledLineThatIntersects({
|
||||||
angle: HALF_TURN,
|
angle = HALF_TURN,
|
||||||
offset: -armThick,
|
offset = -armThick,
|
||||||
intersectTag: seg04
|
intersectTag = seg04
|
||||||
}, %)
|
}, %)
|
||||||
|> angledLineToY([segAng(seg04) + 180, ZERO], %)
|
|> angledLineToY([segAng(seg04) + 180, ZERO], %)
|
||||||
|> angledLineToY({
|
|> angledLineToY({
|
||||||
angle: -bottomAng,
|
angle = -bottomAng,
|
||||||
to: -totalHeightHalf - armThick,
|
to = -totalHeightHalf - armThick,
|
||||||
}, %, $seg02)
|
}, %, $seg02)
|
||||||
|> xLineTo(segEndX(seg03) + 0, %)
|
|> xLineTo(segEndX(seg03) + 0, %)
|
||||||
|> yLine(-segLen(seg01), %)
|
|> yLine(-segLen(seg01), %)
|
||||||
|> angledLineThatIntersects({
|
|> angledLineThatIntersects({
|
||||||
angle: HALF_TURN,
|
angle = HALF_TURN,
|
||||||
offset: -armThick,
|
offset = -armThick,
|
||||||
intersectTag: seg02
|
intersectTag = seg02
|
||||||
}, %)
|
}, %)
|
||||||
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
|
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
|
||||||
|> xLineTo(ZERO, %)
|
|> xLineTo(ZERO, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(4, %)`
|
|> extrude(4, %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
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"]')
|
||||||
await u.waitForCmdReceive('extrude')
|
await u.waitForCmdReceive('extrude')
|
||||||
@ -106,25 +97,14 @@ part001 = startSketchOn('-XZ')
|
|||||||
},
|
},
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is the main thing we're testing,
|
|
||||||
// We test the export functionality across all
|
|
||||||
// file types in snapshot-tests.spec.ts
|
|
||||||
await expect(page.getByText('Exported successfully')).toBeVisible()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Paste should not work unless an input is focused', async ({
|
test('Paste should not work unless an input is focused', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
// To run this test locally, uncomment Firefox in playwright.config.ts
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
test.skip(
|
await homePage.goToModelingScene()
|
||||||
browserName !== 'firefox',
|
|
||||||
"This bug is really Firefox-only, which we don't run in CI."
|
|
||||||
)
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.waitFor({ state: 'visible' })
|
.waitFor({ state: 'visible' })
|
||||||
@ -164,12 +144,12 @@ test('Paste should not work unless an input is focused', async ({
|
|||||||
|
|
||||||
test('Keyboard shortcuts can be viewed through the help menu', async ({
|
test('Keyboard shortcuts can be viewed through the help menu', 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 page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' })
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.waitFor({ state: 'visible' })
|
.waitFor({ state: 'visible' })
|
||||||
@ -181,7 +161,7 @@ test('Keyboard shortcuts can be viewed through the help menu', async ({
|
|||||||
await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click()
|
await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click()
|
||||||
|
|
||||||
// Verify the URL and that you can see a list of shortcuts
|
// Verify the URL and that you can see a list of shortcuts
|
||||||
await expect(page.url()).toContain('?tab=keybindings')
|
await expect.poll(() => page.url()).toContain('?tab=keybindings')
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('heading', { name: 'Enter Sketch Mode' })
|
page.getByRole('heading', { name: 'Enter Sketch Mode' })
|
||||||
).toBeAttached()
|
).toBeAttached()
|
||||||
@ -189,12 +169,13 @@ test('Keyboard shortcuts can be viewed through the help menu', async ({
|
|||||||
|
|
||||||
test('First escape in tool pops you out of tool, second exits sketch mode', async ({
|
test('First escape in tool pops you out of tool, second exits sketch mode', async ({
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
// Wait for the app to be ready for use
|
// Wait for the app to be ready for use
|
||||||
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"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -258,7 +239,7 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
|||||||
|
|
||||||
test.fixme(
|
test.fixme(
|
||||||
'Basic default modeling and sketch hotkeys work',
|
'Basic default modeling and sketch hotkeys work',
|
||||||
async ({ page }) => {
|
async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// This test can run long if it takes a little too long to load
|
// This test can run long if it takes a little too long to load
|
||||||
@ -285,8 +266,8 @@ test.fixme(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
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"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -437,10 +418,11 @@ test.fixme(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test('Delete key does not navigate back', async ({ page }) => {
|
test('Delete key does not navigate back', async ({ page, homePage }) => {
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await page.goto('/')
|
await homePage.goToModelingScene()
|
||||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
|
||||||
|
await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
const settingsButton = page.getByRole('link', {
|
const settingsButton = page.getByRole('link', {
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
@ -449,45 +431,45 @@ test('Delete key does not navigate back', async ({ page }) => {
|
|||||||
const settingsCloseButton = page.getByTestId('settings-close-button')
|
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||||
|
|
||||||
await settingsButton.click()
|
await settingsButton.click()
|
||||||
await expect(page.url()).toContain('/settings')
|
await expect.poll(() => page.url()).toContain('/settings')
|
||||||
|
|
||||||
// Make sure that delete doesn't go back from settings
|
// Make sure that delete doesn't go back from settings
|
||||||
await page.keyboard.press('Delete')
|
await page.keyboard.press('Delete')
|
||||||
await expect(page.url()).toContain('/settings')
|
await expect.poll(() => page.url()).toContain('/settings')
|
||||||
|
|
||||||
// Now close the settings and try delete again,
|
// Now close the settings and try delete again,
|
||||||
// make sure it doesn't go back to settings
|
// make sure it doesn't go back to settings
|
||||||
await settingsCloseButton.click()
|
await settingsCloseButton.click()
|
||||||
await page.keyboard.press('Delete')
|
await page.keyboard.press('Delete')
|
||||||
await expect(page.url()).not.toContain('/settings')
|
await expect.poll(() => page.url()).not.toContain('/settings')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Sketch on face', async ({ page }) => {
|
test('Sketch on face', async ({ page, homePage }) => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
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], %)
|
||||||
|> line([3.75, 0.46], %)
|
|> line([3.75, 0.46], %)
|
||||||
|> line([4.99, -0.46], %)
|
|> line([4.99, -0.46], %)
|
||||||
|> line([3.3, -2.12], %)
|
|> line([3.3, -2.12], %)
|
||||||
|> line([2.16, -3.33], %)
|
|> line([2.16, -3.33], %)
|
||||||
|> line([0.85, -3.08], %)
|
|> line([0.85, -3.08], %)
|
||||||
|> line([-0.18, -3.36], %)
|
|> line([-0.18, -3.36], %)
|
||||||
|> line([-3.86, -2.73], %)
|
|> line([-3.86, -2.73], %)
|
||||||
|> line([-17.67, 0.85], %)
|
|> line([-17.67, 0.85], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(5 + 7, sketch001)`
|
extrude001 = extrude(5 + 7, 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()
|
||||||
@ -541,7 +523,8 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
|> line([2.45, -0.2], %)
|
|> line([2.45, -0.2], %)
|
||||||
|> line([-2.6, -1.25], %)
|
|> line([-2.6, -1.25], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`)
|
|> close(%)
|
||||||
|
`)
|
||||||
)
|
)
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
@ -556,7 +539,7 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(400)
|
await page.waitForTimeout(400)
|
||||||
await page.waitForTimeout(150)
|
await page.waitForTimeout(150)
|
||||||
await page.setViewportSize({ width: 1200, height: 1200 })
|
await page.setBodyDimensions({ width: 1200, height: 1200 })
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.updateCamPosition([452, -152, 1166])
|
await u.updateCamPosition([452, -152, 1166])
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -574,11 +557,11 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
const result = makeTemplate`sketch002 = startSketchOn(extrude001, seg01)
|
const result = makeTemplate`sketch002 = startSketchOn(extrude001, seg01)
|
||||||
|> startProfileAt([-12.83, 6.7], %)
|
|> startProfileAt([-12.83, 6.7], %)
|
||||||
|> line([${[2.28, 2.35]}, -${0.07}], %)
|
|> line([${[2.28, 2.35]}, -${0.07}], %)
|
||||||
|> line([-3.05, -1.47], %)
|
|> line([-3.05, -1.47], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`
|
|> close(%)`
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(result.regExp)
|
await expect(page.locator('.cm-content')).toHaveText(result.regExp)
|
||||||
|
|
||||||
@ -602,6 +585,6 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'checkmark Submit command' }).click()
|
await page.getByRole('button', { name: 'checkmark Submit command' }).click()
|
||||||
|
|
||||||
const result2 = result.genNext`
|
const result2 = result.genNext`
|
||||||
const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
|
const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
|
||||||
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
||||||
})
|
})
|
||||||
|
|||||||
334
e2e/playwright/zoo-test.ts
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
import {
|
||||||
|
test as playwrightTestFn,
|
||||||
|
TestInfo as TestInfoPlaywright,
|
||||||
|
BrowserContext as BrowserContextPlaywright,
|
||||||
|
Page as PagePlaywright,
|
||||||
|
TestDetails as TestDetailsPlaywright,
|
||||||
|
PlaywrightTestArgs,
|
||||||
|
PlaywrightTestOptions,
|
||||||
|
PlaywrightWorkerArgs,
|
||||||
|
PlaywrightWorkerOptions,
|
||||||
|
ElectronApplication,
|
||||||
|
} from '@playwright/test'
|
||||||
|
|
||||||
|
import {
|
||||||
|
fixtures,
|
||||||
|
Fixtures,
|
||||||
|
AuthenticatedTronApp,
|
||||||
|
AuthenticatedApp,
|
||||||
|
} from './fixtures/fixtureSetup'
|
||||||
|
|
||||||
|
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||||
|
export { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
declare module '@playwright/test' {
|
||||||
|
interface TestInfo {
|
||||||
|
tronApp?: AuthenticatedTronApp
|
||||||
|
}
|
||||||
|
interface BrowserContext {
|
||||||
|
folderSetupFn: (
|
||||||
|
cb: (dir: string) => Promise<void>
|
||||||
|
) => Promise<{ dir: string }>
|
||||||
|
}
|
||||||
|
interface Page {
|
||||||
|
dir: string
|
||||||
|
TEST_SETTINGS_FILE_KEY?: string
|
||||||
|
setBodyDimensions: (dims: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}) => Promise<void>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TestInfo = TestInfoPlaywright
|
||||||
|
export type BrowserContext = BrowserContextPlaywright
|
||||||
|
export type Page = PagePlaywright
|
||||||
|
export type TestDetails = TestDetailsPlaywright & {
|
||||||
|
cleanProjectDir?: boolean
|
||||||
|
appSettings?: Partial<SaveSettingsPayload>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and
|
||||||
|
// switch between web and electron if needed.
|
||||||
|
const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(fixtures)
|
||||||
|
|
||||||
|
// In JavaScript you cannot replace a function's body only (despite functions
|
||||||
|
// are themselves objects, which you'd expect a body property or something...)
|
||||||
|
// So we must redefine the function and then re-attach properties.
|
||||||
|
type PWFunction = (
|
||||||
|
args: PlaywrightTestArgs &
|
||||||
|
Fixtures &
|
||||||
|
PlaywrightWorkerArgs &
|
||||||
|
PlaywrightTestOptions &
|
||||||
|
PlaywrightWorkerOptions & {
|
||||||
|
electronApp?: ElectronApplication
|
||||||
|
},
|
||||||
|
testInfo: TestInfo
|
||||||
|
) => void | Promise<void>
|
||||||
|
|
||||||
|
let firstUrl = ''
|
||||||
|
|
||||||
|
// The below error is due to the extreme type spaghetti going on. playwright/
|
||||||
|
// types/test.d.ts does not export 2 functions (below is one of them) but tsc
|
||||||
|
// is trying to use a interface name it can't see.
|
||||||
|
// e2e/playwright/zoo-test.ts:64:14 - error TS4023: Exported variable 'test' has
|
||||||
|
// or is using name 'TestFunction' from external module
|
||||||
|
// "/home/lee/Code/Zoo/modeling-app/dirty2/node_modules/playwright/types/test"
|
||||||
|
// but cannot be named.
|
||||||
|
export const test = (
|
||||||
|
desc: string,
|
||||||
|
objOrFn: PWFunction | TestDetails,
|
||||||
|
fnMaybe?: PWFunction
|
||||||
|
) => {
|
||||||
|
const hasTestConf = typeof objOrFn === 'object'
|
||||||
|
const fn = hasTestConf ? fnMaybe : objOrFn
|
||||||
|
|
||||||
|
return pwTestFnWithFixtures(
|
||||||
|
desc,
|
||||||
|
hasTestConf ? objOrFn : {},
|
||||||
|
async (
|
||||||
|
{
|
||||||
|
page,
|
||||||
|
context,
|
||||||
|
cmdBar,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
scene,
|
||||||
|
homePage,
|
||||||
|
request,
|
||||||
|
playwright,
|
||||||
|
browser,
|
||||||
|
acceptDownloads,
|
||||||
|
bypassCSP,
|
||||||
|
colorScheme,
|
||||||
|
clientCertificates,
|
||||||
|
deviceScaleFactor,
|
||||||
|
extraHTTPHeaders,
|
||||||
|
geolocation,
|
||||||
|
hasTouch,
|
||||||
|
httpCredentials,
|
||||||
|
ignoreHTTPSErrors,
|
||||||
|
isMobile,
|
||||||
|
javaScriptEnabled,
|
||||||
|
locale,
|
||||||
|
offline,
|
||||||
|
permissions,
|
||||||
|
proxy,
|
||||||
|
storageState,
|
||||||
|
timezoneId,
|
||||||
|
userAgent,
|
||||||
|
viewport,
|
||||||
|
baseURL,
|
||||||
|
contextOptions,
|
||||||
|
actionTimeout,
|
||||||
|
navigationTimeout,
|
||||||
|
serviceWorkers,
|
||||||
|
testIdAttribute,
|
||||||
|
browserName,
|
||||||
|
defaultBrowserType,
|
||||||
|
headless,
|
||||||
|
channel,
|
||||||
|
launchOptions,
|
||||||
|
connectOptions,
|
||||||
|
screenshot,
|
||||||
|
trace,
|
||||||
|
video,
|
||||||
|
},
|
||||||
|
testInfo
|
||||||
|
) => {
|
||||||
|
// To switch to web, use PLATFORM=web environment variable.
|
||||||
|
// Only use this for debugging, since the playwright tracer is busted
|
||||||
|
// for electron.
|
||||||
|
|
||||||
|
let tronApp
|
||||||
|
|
||||||
|
if (process.env.PLATFORM === 'web') {
|
||||||
|
tronApp = new AuthenticatedApp(context, page, testInfo)
|
||||||
|
} else {
|
||||||
|
tronApp = new AuthenticatedTronApp(context, page, testInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixtures: Fixtures = { cmdBar, editor, toolbar, scene, homePage }
|
||||||
|
if (tronApp instanceof AuthenticatedTronApp) {
|
||||||
|
const options = {
|
||||||
|
fixtures,
|
||||||
|
}
|
||||||
|
if (hasTestConf) {
|
||||||
|
Object.assign(options, {
|
||||||
|
appSettings: objOrFn?.appSettings,
|
||||||
|
cleanProjectDir: objOrFn?.cleanProjectDir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await tronApp.initialise(options)
|
||||||
|
} else {
|
||||||
|
await tronApp.initialise('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to patch this because addInitScript will bind too late in our
|
||||||
|
// electron tests, never running. We need to call reload() after each call
|
||||||
|
// to guarantee it runs.
|
||||||
|
const oldContextAddInitScript = tronApp.context.addInitScript
|
||||||
|
tronApp.context.addInitScript = async function (a, b) {
|
||||||
|
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
||||||
|
// This code works perfectly fine.
|
||||||
|
await oldContextAddInitScript.apply(this, [a, b])
|
||||||
|
await tronApp.page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
// No idea why we mix and match page and context's addInitScript but we do
|
||||||
|
const oldPageAddInitScript = tronApp.page.addInitScript
|
||||||
|
tronApp.page.addInitScript = async function (a: any, b: any) {
|
||||||
|
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
||||||
|
// This code works perfectly fine.
|
||||||
|
await oldPageAddInitScript.apply(this, [a, b])
|
||||||
|
await tronApp.page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a consistent way to resize the page across electron and web.
|
||||||
|
// (lee) I had to do everyhting in the book to make electron change its
|
||||||
|
// damn window size. I succeded in making it consistently and reliably
|
||||||
|
// do it after a whole afternoon.
|
||||||
|
tronApp.page.setBodyDimensions = async function (dims: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}) {
|
||||||
|
await tronApp.page.setViewportSize(dims)
|
||||||
|
|
||||||
|
if (!(tronApp instanceof AuthenticatedTronApp)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.electronApp?.evaluateHandle(async ({ app }, dims) => {
|
||||||
|
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
|
||||||
|
await app.resizeWindow(dims.width, dims.height)
|
||||||
|
}, dims)
|
||||||
|
|
||||||
|
return tronApp.page.evaluate(
|
||||||
|
async (dims: { width: number; height: number }) => {
|
||||||
|
await window.electron.resizeWindow(dims.width, dims.height)
|
||||||
|
window.document.body.style.width = dims.width + 'px'
|
||||||
|
window.document.body.style.height = dims.height + 'px'
|
||||||
|
window.document.documentElement.style.width = dims.width + 'px'
|
||||||
|
window.document.documentElement.style.height = dims.height + 'px'
|
||||||
|
},
|
||||||
|
dims
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.page.setBodyDimensions(tronApp.viewPortSize)
|
||||||
|
|
||||||
|
// We need to expose this in order for some tests that require folder
|
||||||
|
// creation. Before they used to do this by their own electronSetup({...})
|
||||||
|
// calls.
|
||||||
|
if (tronApp instanceof AuthenticatedTronApp) {
|
||||||
|
tronApp.context.folderSetupFn = async function (fn) {
|
||||||
|
return fn(tronApp.dir)
|
||||||
|
.then(() => tronApp.page.reload())
|
||||||
|
.then(() => ({
|
||||||
|
dir: tronApp.dir,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!firstUrl) {
|
||||||
|
await tronApp.page.getByText('Your Projects').count()
|
||||||
|
firstUrl = tronApp.page.url()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to the app controlling its own window context we need to inject new
|
||||||
|
// options and context here.
|
||||||
|
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
|
||||||
|
// await tronApp.electronApp.evaluate(({ app }) => {
|
||||||
|
// return app.reuseWindowForTest();
|
||||||
|
// });
|
||||||
|
|
||||||
|
await tronApp.electronApp.evaluate(({ app }, projectDirName) => {
|
||||||
|
console.log('ABCDEFGHI', app.testProperty['TEST_SETTINGS_FILE_KEY'])
|
||||||
|
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
|
||||||
|
}, tronApp.dir)
|
||||||
|
|
||||||
|
// Always start at the root view
|
||||||
|
await tronApp.page.goto(firstUrl)
|
||||||
|
|
||||||
|
// Force a hard reload, destroying the stream and other state
|
||||||
|
await tronApp.page.reload()
|
||||||
|
|
||||||
|
// tsc aint smart enough to know this'll never be undefined
|
||||||
|
// but I dont blame it, the logic to know is complex
|
||||||
|
if (fn) {
|
||||||
|
await fn(
|
||||||
|
{
|
||||||
|
context: tronApp.context,
|
||||||
|
page: tronApp.page,
|
||||||
|
electronApp:
|
||||||
|
tronApp instanceof AuthenticatedTronApp
|
||||||
|
? tronApp.electronApp
|
||||||
|
: undefined,
|
||||||
|
...fixtures,
|
||||||
|
request,
|
||||||
|
playwright,
|
||||||
|
browser,
|
||||||
|
acceptDownloads,
|
||||||
|
bypassCSP,
|
||||||
|
colorScheme,
|
||||||
|
clientCertificates,
|
||||||
|
deviceScaleFactor,
|
||||||
|
extraHTTPHeaders,
|
||||||
|
geolocation,
|
||||||
|
hasTouch,
|
||||||
|
httpCredentials,
|
||||||
|
ignoreHTTPSErrors,
|
||||||
|
isMobile,
|
||||||
|
javaScriptEnabled,
|
||||||
|
locale,
|
||||||
|
offline,
|
||||||
|
permissions,
|
||||||
|
proxy,
|
||||||
|
storageState,
|
||||||
|
timezoneId,
|
||||||
|
userAgent,
|
||||||
|
viewport,
|
||||||
|
baseURL,
|
||||||
|
contextOptions,
|
||||||
|
actionTimeout,
|
||||||
|
navigationTimeout,
|
||||||
|
serviceWorkers,
|
||||||
|
testIdAttribute,
|
||||||
|
browserName,
|
||||||
|
defaultBrowserType,
|
||||||
|
headless,
|
||||||
|
channel,
|
||||||
|
launchOptions,
|
||||||
|
connectOptions,
|
||||||
|
screenshot,
|
||||||
|
trace,
|
||||||
|
video,
|
||||||
|
},
|
||||||
|
testInfo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
testInfo.tronApp =
|
||||||
|
tronApp instanceof AuthenticatedTronApp ? tronApp : undefined
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZooTest = typeof test
|
||||||
|
|
||||||
|
test.describe = pwTestFnWithFixtures.describe
|
||||||
|
test.beforeEach = pwTestFnWithFixtures.beforeEach
|
||||||
|
test.afterEach = pwTestFnWithFixtures.afterEach
|
||||||
|
test.step = pwTestFnWithFixtures.step
|
||||||
|
test.skip = pwTestFnWithFixtures.skip
|
||||||
|
test.setTimeout = pwTestFnWithFixtures.setTimeout
|
||||||
|
test.fixme = pwTestFnWithFixtures.fixme as unknown as ZooTest
|
||||||
|
test.only = pwTestFnWithFixtures.only
|
||||||
|
test.fail = pwTestFnWithFixtures.fail
|
||||||
|
test.slow = pwTestFnWithFixtures.slow
|
||||||
|
test.beforeAll = pwTestFnWithFixtures.beforeAll
|
||||||
|
test.afterAll = pwTestFnWithFixtures.afterAll
|
||||||
|
test.use = pwTestFnWithFixtures.use
|
||||||
|
test.expect = pwTestFnWithFixtures.expect
|
||||||
|
test.extend = pwTestFnWithFixtures.extend
|
||||||
|
test.info = pwTestFnWithFixtures.info
|
||||||
1
interface.d.ts
vendored
@ -7,6 +7,7 @@ import { MachinesListing } from 'components/MachineManagerProvider'
|
|||||||
type EnvFn = (value?: string) => string
|
type EnvFn = (value?: string) => string
|
||||||
|
|
||||||
export interface IElectronAPI {
|
export interface IElectronAPI {
|
||||||
|
resizeWindow: (width: number, height: number) => Promise<void>
|
||||||
open: typeof dialog.showOpenDialog
|
open: typeof dialog.showOpenDialog
|
||||||
save: typeof dialog.showSaveDialog
|
save: typeof dialog.showSaveDialog
|
||||||
openExternal: typeof shell.openExternal
|
openExternal: typeof shell.openExternal
|
||||||
|
|||||||
26
package.json
@ -103,24 +103,24 @@
|
|||||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||||
"tron:start": "electron-forge start",
|
"tron:start": "electron-forge start",
|
||||||
"tron:package": "electron-forge package",
|
"tron:package": "electron-forge package",
|
||||||
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron",
|
"tron:make": "electron-forge make",
|
||||||
|
"tron:publish": "electron-forge publish",
|
||||||
|
"chrome:test": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'",
|
||||||
|
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
|
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
|
||||||
"tronb:package": "electron-builder --config electron-builder.yml",
|
"tronb:package": "electron-builder --config electron-builder.yml",
|
||||||
"test-setup": "yarn install && yarn build:wasm",
|
"test-setup": "yarn install && yarn build:wasm",
|
||||||
"test": "vitest --mode development",
|
"test": "vitest --mode development",
|
||||||
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
||||||
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
||||||
"test:playwright:browser:chrome": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron'",
|
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"test:playwright:browser:chrome:windows": "playwright test --project=\"Google Chrome\" --config=playwright.ci.config.ts --grep-invert=\"@snapshot|@electron|@skipWin\"",
|
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipWin|@snapshot'",
|
||||||
"test:playwright:browser:chrome:ubuntu": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron|@skipLinux'",
|
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||||
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep=@electron",
|
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
||||||
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin",
|
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos",
|
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipWin|@snapshot'",
|
||||||
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux",
|
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||||
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron",
|
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
||||||
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin",
|
|
||||||
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos",
|
|
||||||
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux",
|
|
||||||
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
||||||
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
|
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
|
||||||
},
|
},
|
||||||
@ -152,7 +152,7 @@
|
|||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@lezer/generator": "^1.7.1",
|
"@lezer/generator": "^1.7.1",
|
||||||
"@nabla/vite-plugin-eslint": "^2.0.5",
|
"@nabla/vite-plugin-eslint": "^2.0.5",
|
||||||
"@playwright/test": "^1.46.1",
|
"@playwright/test": "^1.49.0",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^15.0.2",
|
"@testing-library/react": "^15.0.2",
|
||||||
"@types/d3-force": "^3.0.10",
|
"@types/d3-force": "^3.0.10",
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
|
||||||
*/
|
|
||||||
export default defineConfig({
|
|
||||||
timeout: 120_000, // override the default 30s timeout
|
|
||||||
testDir: './e2e/playwright',
|
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: true,
|
|
||||||
/* Do not retry */
|
|
||||||
retries: 0,
|
|
||||||
/* Different amount of parallelism on CI and local. */
|
|
||||||
workers: 1,
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: [
|
|
||||||
['dot'],
|
|
||||||
['list'],
|
|
||||||
['json', { outputFile: './test-results/report.json' }],
|
|
||||||
['html'],
|
|
||||||
],
|
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
||||||
use: {
|
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
||||||
baseURL: 'http://localhost:3000',
|
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: 'retain-on-failure',
|
|
||||||
actionTimeout: 15_000,
|
|
||||||
screenshot: 'only-on-failure',
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: 'Google Chrome',
|
|
||||||
use: {
|
|
||||||
...devices['Desktop Chrome'],
|
|
||||||
channel: 'chrome',
|
|
||||||
contextOptions: {
|
|
||||||
/* Chromium is the only one with these permission types */
|
|
||||||
permissions: ['clipboard-write', 'clipboard-read'],
|
|
||||||
},
|
|
||||||
}, // or 'chrome-beta'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'webkit',
|
|
||||||
use: { ...devices['Desktop Safari'] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
webServer: {
|
|
||||||
command: 'yarn start',
|
|
||||||
reuseExistingServer: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@ -13,7 +13,7 @@ export default defineConfig({
|
|||||||
/* Do not retry */
|
/* Do not retry */
|
||||||
retries: 0,
|
retries: 0,
|
||||||
/* Different amount of parallelism on CI and local. */
|
/* Different amount of parallelism on CI and local. */
|
||||||
workers: 1,
|
workers: 30,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: [
|
reporter: [
|
||||||
['dot'],
|
['dot'],
|
||||||
|
|||||||
@ -105,7 +105,7 @@ export class CameraControls {
|
|||||||
pendingZoom: number | null = null
|
pendingZoom: number | null = null
|
||||||
pendingRotation: Vector2 | null = null
|
pendingRotation: Vector2 | null = null
|
||||||
pendingPan: Vector2 | null = null
|
pendingPan: Vector2 | null = null
|
||||||
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo
|
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
|
||||||
isFovAnimationInProgress = false
|
isFovAnimationInProgress = false
|
||||||
perspectiveFovBeforeOrtho = 45
|
perspectiveFovBeforeOrtho = 45
|
||||||
get isPerspective() {
|
get isPerspective() {
|
||||||
|
|||||||
@ -1,23 +1,13 @@
|
|||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||||
import {
|
import { RefObject, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
MouseEvent,
|
|
||||||
RefObject,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { Dialog } from '@headlessui/react'
|
import { Dialog } from '@headlessui/react'
|
||||||
|
|
||||||
export interface ContextMenuProps
|
interface ContextMenuProps
|
||||||
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
|
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
|
||||||
items?: React.ReactElement[]
|
items?: React.ReactElement[]
|
||||||
menuTargetElement?: RefObject<HTMLElement>
|
menuTargetElement?: RefObject<HTMLElement>
|
||||||
guard?: (e: globalThis.MouseEvent) => boolean
|
|
||||||
event?: 'contextmenu' | 'mouseup'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultContextMenuItems = [
|
const DefaultContextMenuItems = [
|
||||||
@ -30,8 +20,6 @@ export function ContextMenu({
|
|||||||
items = DefaultContextMenuItems,
|
items = DefaultContextMenuItems,
|
||||||
menuTargetElement,
|
menuTargetElement,
|
||||||
className,
|
className,
|
||||||
guard,
|
|
||||||
event = 'contextmenu',
|
|
||||||
...props
|
...props
|
||||||
}: ContextMenuProps) {
|
}: ContextMenuProps) {
|
||||||
const dialogRef = useRef<HTMLDivElement>(null)
|
const dialogRef = useRef<HTMLDivElement>(null)
|
||||||
@ -44,15 +32,6 @@ export function ContextMenu({
|
|||||||
useHotkeys('esc', () => setOpen(false), {
|
useHotkeys('esc', () => setOpen(false), {
|
||||||
enabled: open,
|
enabled: open,
|
||||||
})
|
})
|
||||||
const handleContextMenu = useCallback(
|
|
||||||
(e: globalThis.MouseEvent) => {
|
|
||||||
if (guard && !guard(e)) return
|
|
||||||
e.preventDefault()
|
|
||||||
setPosition({ x: e.clientX, y: e.clientY })
|
|
||||||
setOpen(true)
|
|
||||||
},
|
|
||||||
[guard, setPosition, setOpen]
|
|
||||||
)
|
|
||||||
|
|
||||||
const dialogPositionStyle = useMemo(() => {
|
const dialogPositionStyle = useMemo(() => {
|
||||||
if (!dialogRef.current)
|
if (!dialogRef.current)
|
||||||
@ -99,9 +78,21 @@ export function ContextMenu({
|
|||||||
|
|
||||||
// Add context menu listener to target once mounted
|
// Add context menu listener to target once mounted
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
menuTargetElement?.current?.addEventListener(event, handleContextMenu)
|
const handleContextMenu = (e: MouseEvent) => {
|
||||||
|
console.log('context menu', e)
|
||||||
|
e.preventDefault()
|
||||||
|
setPosition({ x: e.x, y: e.y })
|
||||||
|
setOpen(true)
|
||||||
|
}
|
||||||
|
menuTargetElement?.current?.addEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
handleContextMenu
|
||||||
|
)
|
||||||
return () => {
|
return () => {
|
||||||
menuTargetElement?.current?.removeEventListener(event, handleContextMenu)
|
menuTargetElement?.current?.removeEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
handleContextMenu
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, [menuTargetElement?.current])
|
}, [menuTargetElement?.current])
|
||||||
|
|
||||||
@ -109,10 +100,7 @@ export function ContextMenu({
|
|||||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-50 w-screen h-screen"
|
className="fixed inset-0 z-50 w-screen h-screen"
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
e.preventDefault()
|
|
||||||
setPosition({ x: e.clientX, y: e.clientY })
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Dialog.Backdrop className="fixed z-10 inset-0" />
|
<Dialog.Backdrop className="fixed z-10 inset-0" />
|
||||||
<Dialog.Panel
|
<Dialog.Panel
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
import { sceneInfra } from 'lib/singletons'
|
import { sceneInfra } from 'lib/singletons'
|
||||||
import { MutableRefObject, useEffect, useRef } from 'react'
|
import { MutableRefObject, useEffect, useMemo, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
WebGLRenderer,
|
WebGLRenderer,
|
||||||
Scene,
|
Scene,
|
||||||
@ -19,14 +19,16 @@ import {
|
|||||||
Intersection,
|
Intersection,
|
||||||
Object3D,
|
Object3D,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuDivider,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuItemRefresh,
|
||||||
|
} from './ContextMenu'
|
||||||
import { Popover } from '@headlessui/react'
|
import { Popover } from '@headlessui/react'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import {
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
useViewControlMenuItems,
|
|
||||||
ViewControlContextMenu,
|
|
||||||
} from './ViewControlMenu'
|
|
||||||
import { AxisNames } from 'lib/constants'
|
|
||||||
|
|
||||||
const CANVAS_SIZE = 80
|
const CANVAS_SIZE = 80
|
||||||
const FRUSTUM_SIZE = 0.5
|
const FRUSTUM_SIZE = 0.5
|
||||||
@ -38,14 +40,64 @@ enum AxisColors {
|
|||||||
Z = '#6689ef',
|
Z = '#6689ef',
|
||||||
Gray = '#c6c7c2',
|
Gray = '#c6c7c2',
|
||||||
}
|
}
|
||||||
|
enum AxisNames {
|
||||||
|
X = 'x',
|
||||||
|
Y = 'y',
|
||||||
|
Z = 'z',
|
||||||
|
NEG_X = '-x',
|
||||||
|
NEG_Y = '-y',
|
||||||
|
NEG_Z = '-z',
|
||||||
|
}
|
||||||
|
const axisNamesSemantic: Record<AxisNames, string> = {
|
||||||
|
[AxisNames.X]: 'Right',
|
||||||
|
[AxisNames.Y]: 'Back',
|
||||||
|
[AxisNames.Z]: 'Top',
|
||||||
|
[AxisNames.NEG_X]: 'Left',
|
||||||
|
[AxisNames.NEG_Y]: 'Front',
|
||||||
|
[AxisNames.NEG_Z]: 'Bottom',
|
||||||
|
}
|
||||||
|
|
||||||
export default function Gizmo() {
|
export default function Gizmo() {
|
||||||
const menuItems = useViewControlMenuItems()
|
|
||||||
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||||
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
||||||
const cameraPassiveUpdateTimer = useRef(0)
|
const cameraPassiveUpdateTimer = useRef(0)
|
||||||
const raycasterPassiveUpdateTimer = useRef(0)
|
const raycasterPassiveUpdateTimer = useRef(0)
|
||||||
|
const { send: modelingSend } = useModelingContext()
|
||||||
|
const menuItems = useMemo(
|
||||||
|
() => [
|
||||||
|
...Object.entries(axisNamesSemantic).map(([axisName, axisSemantic]) => (
|
||||||
|
<ContextMenuItem
|
||||||
|
key={axisName}
|
||||||
|
onClick={() => {
|
||||||
|
sceneInfra.camControls
|
||||||
|
.updateCameraToAxis(axisName as AxisNames)
|
||||||
|
.catch(reportRejection)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{axisSemantic} view
|
||||||
|
</ContextMenuItem>
|
||||||
|
)),
|
||||||
|
<ContextMenuDivider />,
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset view
|
||||||
|
</ContextMenuItem>,
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
modelingSend({ type: 'Center camera on selection' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Center view on selection
|
||||||
|
</ContextMenuItem>,
|
||||||
|
<ContextMenuDivider />,
|
||||||
|
<ContextMenuItemRefresh />,
|
||||||
|
],
|
||||||
|
[axisNamesSemantic]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canvasRef.current) return
|
if (!canvasRef.current) return
|
||||||
@ -109,7 +161,7 @@ export default function Gizmo() {
|
|||||||
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
||||||
>
|
>
|
||||||
<canvas ref={canvasRef} />
|
<canvas ref={canvasRef} />
|
||||||
<ViewControlContextMenu menuTargetElement={wrapperRef} />
|
<ContextMenu menuTargetElement={wrapperRef} items={menuItems} />
|
||||||
</div>
|
</div>
|
||||||
<GizmoDropdown items={menuItems} />
|
<GizmoDropdown items={menuItems} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import Tooltip from 'components/Tooltip'
|
|||||||
import { CustomIconName } from 'components/CustomIcon'
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionIcon } from 'components/ActionIcon'
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
|
||||||
|
|
||||||
export interface ModelingPaneProps {
|
export interface ModelingPaneProps {
|
||||||
id: string
|
id: string
|
||||||
@ -71,7 +70,7 @@ export const ModelingPane = ({
|
|||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const onboardingStatus = settings.context.app.onboardingStatus
|
const onboardingStatus = settings.context.app.onboardingStatus
|
||||||
const pointerEventsCssClass =
|
const pointerEventsCssClass =
|
||||||
onboardingStatus.current === onboardingPaths.CAMERA
|
onboardingStatus.current === 'camera'
|
||||||
? 'pointer-events-none '
|
? 'pointer-events-none '
|
||||||
: 'pointer-events-auto '
|
: 'pointer-events-auto '
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
|||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -42,7 +41,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
const onboardingStatus = settings.context.app.onboardingStatus
|
const onboardingStatus = settings.context.app.onboardingStatus
|
||||||
const { send, context } = useModelingContext()
|
const { send, context } = useModelingContext()
|
||||||
const pointerEventsCssClass =
|
const pointerEventsCssClass =
|
||||||
onboardingStatus.current === onboardingPaths.CAMERA ||
|
onboardingStatus.current === 'camera' ||
|
||||||
context.store?.openPanes.length === 0
|
context.store?.openPanes.length === 0
|
||||||
? 'pointer-events-none '
|
? 'pointer-events-none '
|
||||||
: 'pointer-events-auto '
|
: 'pointer-events-auto '
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import { IndexLoaderData } from 'lib/types'
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { err, reportRejection } from 'lib/trap'
|
import { err, reportRejection } from 'lib/trap'
|
||||||
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||||
import { ViewControlContextMenu } from './ViewControlMenu'
|
|
||||||
|
|
||||||
enum StreamState {
|
enum StreamState {
|
||||||
Playing = 'playing',
|
Playing = 'playing',
|
||||||
@ -31,7 +30,6 @@ enum StreamState {
|
|||||||
|
|
||||||
export const Stream = () => {
|
export const Stream = () => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const videoWrapperRef = useRef<HTMLDivElement>(null)
|
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const { state, send } = useModelingContext()
|
const { state, send } = useModelingContext()
|
||||||
@ -260,7 +258,7 @@ export const Stream = () => {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}, [mediaStream])
|
}, [mediaStream])
|
||||||
|
|
||||||
const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
|
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
// If we've got no stream or connection, don't do anything
|
// If we've got no stream or connection, don't do anything
|
||||||
if (!isNetworkOkay) return
|
if (!isNetworkOkay) return
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
@ -322,11 +320,10 @@ export const Stream = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={videoWrapperRef}
|
|
||||||
className="absolute inset-0 z-0"
|
className="absolute inset-0 z-0"
|
||||||
id="stream"
|
id="stream"
|
||||||
data-testid="stream"
|
data-testid="stream"
|
||||||
onClick={handleClick}
|
onClick={handleMouseUp}
|
||||||
onDoubleClick={enterSketchModeIfSelectingSketch}
|
onDoubleClick={enterSketchModeIfSelectingSketch}
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
onContextMenuCapture={(e) => e.preventDefault()}
|
onContextMenuCapture={(e) => e.preventDefault()}
|
||||||
@ -387,14 +384,6 @@ export const Stream = () => {
|
|||||||
</Loading>
|
</Loading>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ViewControlContextMenu
|
|
||||||
event="mouseup"
|
|
||||||
guard={(e) =>
|
|
||||||
sceneInfra.camControls.wasDragging === false &&
|
|
||||||
btnName(e).right === true
|
|
||||||
}
|
|
||||||
menuTargetElement={videoWrapperRef}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,66 +0,0 @@
|
|||||||
import { reportRejection } from 'lib/trap'
|
|
||||||
import {
|
|
||||||
ContextMenu,
|
|
||||||
ContextMenuDivider,
|
|
||||||
ContextMenuItem,
|
|
||||||
ContextMenuItemRefresh,
|
|
||||||
ContextMenuProps,
|
|
||||||
} from './ContextMenu'
|
|
||||||
import { AxisNames, VIEW_NAMES_SEMANTIC } from 'lib/constants'
|
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
|
||||||
import { useMemo } from 'react'
|
|
||||||
import { sceneInfra } from 'lib/singletons'
|
|
||||||
|
|
||||||
export function useViewControlMenuItems() {
|
|
||||||
const { send: modelingSend } = useModelingContext()
|
|
||||||
const menuItems = useMemo(
|
|
||||||
() => [
|
|
||||||
...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => (
|
|
||||||
<ContextMenuItem
|
|
||||||
key={axisName}
|
|
||||||
onClick={() => {
|
|
||||||
sceneInfra.camControls
|
|
||||||
.updateCameraToAxis(axisName as AxisNames)
|
|
||||||
.catch(reportRejection)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{axisSemantic} view
|
|
||||||
</ContextMenuItem>
|
|
||||||
)),
|
|
||||||
<ContextMenuDivider />,
|
|
||||||
<ContextMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset view
|
|
||||||
</ContextMenuItem>,
|
|
||||||
<ContextMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
modelingSend({ type: 'Center camera on selection' })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Center view on selection
|
|
||||||
</ContextMenuItem>,
|
|
||||||
<ContextMenuDivider />,
|
|
||||||
<ContextMenuItemRefresh />,
|
|
||||||
],
|
|
||||||
[VIEW_NAMES_SEMANTIC]
|
|
||||||
)
|
|
||||||
return menuItems
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ViewControlContextMenu({
|
|
||||||
menuTargetElement: wrapperRef,
|
|
||||||
...props
|
|
||||||
}: ContextMenuProps) {
|
|
||||||
const menuItems = useViewControlMenuItems()
|
|
||||||
return (
|
|
||||||
<ContextMenu
|
|
||||||
data-testid="view-controls-menu"
|
|
||||||
menuTargetElement={wrapperRef}
|
|
||||||
items={menuItems}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,327 +0,0 @@
|
|||||||
import {
|
|
||||||
EditorView,
|
|
||||||
WidgetType,
|
|
||||||
ViewUpdate,
|
|
||||||
ViewPlugin,
|
|
||||||
DecorationSet,
|
|
||||||
Decoration,
|
|
||||||
} from '@codemirror/view'
|
|
||||||
import { Range, Extension, Text } from '@codemirror/state'
|
|
||||||
import { NodeProp, Tree } from '@lezer/common'
|
|
||||||
import { language, syntaxTree } from '@codemirror/language'
|
|
||||||
|
|
||||||
interface PickerState {
|
|
||||||
from: number
|
|
||||||
to: number
|
|
||||||
alpha: string
|
|
||||||
colorType: ColorType
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WidgetOptions extends PickerState {
|
|
||||||
color: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ColorData = Omit<WidgetOptions, 'from' | 'to'>
|
|
||||||
|
|
||||||
const pickerState = new WeakMap<HTMLInputElement, PickerState>()
|
|
||||||
|
|
||||||
export enum ColorType {
|
|
||||||
hex = 'HEX',
|
|
||||||
}
|
|
||||||
|
|
||||||
const hexRegex = /(^|\b)(#[0-9a-f]{3,9})(\b|$)/i
|
|
||||||
|
|
||||||
function discoverColorsInKCL(
|
|
||||||
syntaxTree: Tree,
|
|
||||||
from: number,
|
|
||||||
to: number,
|
|
||||||
typeName: string,
|
|
||||||
doc: Text,
|
|
||||||
language?: string
|
|
||||||
): WidgetOptions | Array<WidgetOptions> | null {
|
|
||||||
switch (typeName) {
|
|
||||||
case 'Program':
|
|
||||||
case 'VariableDeclaration':
|
|
||||||
case 'CallExpression':
|
|
||||||
case 'ObjectExpression':
|
|
||||||
case 'ObjectProperty':
|
|
||||||
case 'ArgumentList':
|
|
||||||
case 'PipeExpression': {
|
|
||||||
let innerTree = syntaxTree.resolveInner(from, 0).tree
|
|
||||||
|
|
||||||
if (!innerTree) {
|
|
||||||
innerTree = syntaxTree.resolveInner(from, 1).tree
|
|
||||||
if (!innerTree) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const overlayTree = innerTree.prop(NodeProp.mounted)?.tree
|
|
||||||
|
|
||||||
if (overlayTree?.type.name !== 'Styles') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const ret: Array<WidgetOptions> = []
|
|
||||||
overlayTree.iterate({
|
|
||||||
from: 0,
|
|
||||||
to: overlayTree.length,
|
|
||||||
enter: ({ type, from: overlayFrom, to: overlayTo }) => {
|
|
||||||
const maybeWidgetOptions = discoverColorsInKCL(
|
|
||||||
syntaxTree,
|
|
||||||
// We add one because the tree doesn't include the
|
|
||||||
// quotation mark from the style tag
|
|
||||||
from + 1 + overlayFrom,
|
|
||||||
from + 1 + overlayTo,
|
|
||||||
type.name,
|
|
||||||
doc,
|
|
||||||
language
|
|
||||||
)
|
|
||||||
|
|
||||||
if (maybeWidgetOptions) {
|
|
||||||
if (Array.isArray(maybeWidgetOptions)) {
|
|
||||||
console.error('Unexpected nested overlays')
|
|
||||||
ret.push(...maybeWidgetOptions)
|
|
||||||
} else {
|
|
||||||
ret.push(maybeWidgetOptions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'String': {
|
|
||||||
const result = parseColorLiteral(doc.sliceString(from, to))
|
|
||||||
if (!result) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...result,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseColorLiteral(colorLiteral: string): ColorData | null {
|
|
||||||
const literal = colorLiteral.replace(/"/g, '')
|
|
||||||
const match = hexRegex.exec(literal)
|
|
||||||
if (!match) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const [color, alpha] = toFullHex(literal)
|
|
||||||
|
|
||||||
return {
|
|
||||||
colorType: ColorType.hex,
|
|
||||||
color,
|
|
||||||
alpha,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function colorPickersDecorations(
|
|
||||||
view: EditorView,
|
|
||||||
discoverColors: typeof discoverColorsInKCL
|
|
||||||
) {
|
|
||||||
const widgets: Array<Range<Decoration>> = []
|
|
||||||
|
|
||||||
const st = syntaxTree(view.state)
|
|
||||||
|
|
||||||
for (const range of view.visibleRanges) {
|
|
||||||
st.iterate({
|
|
||||||
from: range.from,
|
|
||||||
to: range.to,
|
|
||||||
enter: ({ type, from, to }) => {
|
|
||||||
const maybeWidgetOptions = discoverColors(
|
|
||||||
st,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
type.name,
|
|
||||||
view.state.doc,
|
|
||||||
view.state.facet(language)?.name
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!maybeWidgetOptions) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(maybeWidgetOptions)) {
|
|
||||||
widgets.push(
|
|
||||||
Decoration.widget({
|
|
||||||
widget: new ColorPickerWidget(maybeWidgetOptions),
|
|
||||||
side: 1,
|
|
||||||
}).range(maybeWidgetOptions.from)
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const wo of maybeWidgetOptions) {
|
|
||||||
widgets.push(
|
|
||||||
Decoration.widget({
|
|
||||||
widget: new ColorPickerWidget(wo),
|
|
||||||
side: 1,
|
|
||||||
}).range(wo.from)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return Decoration.set(widgets)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toFullHex(color: string): string[] {
|
|
||||||
if (color.length === 4) {
|
|
||||||
// 3-char hex
|
|
||||||
return [
|
|
||||||
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
|
|
||||||
'',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color.length === 5) {
|
|
||||||
// 4-char hex (alpha)
|
|
||||||
return [
|
|
||||||
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
|
|
||||||
color[4].repeat(2),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color.length === 9) {
|
|
||||||
// 8-char hex (alpha)
|
|
||||||
return [`#${color.slice(1, -2)}`, color.slice(-2)]
|
|
||||||
}
|
|
||||||
|
|
||||||
return [color, '']
|
|
||||||
}
|
|
||||||
|
|
||||||
export const wrapperClassName = 'cm-css-color-picker-wrapper'
|
|
||||||
|
|
||||||
class ColorPickerWidget extends WidgetType {
|
|
||||||
private readonly state: PickerState
|
|
||||||
private readonly color: string
|
|
||||||
|
|
||||||
constructor({ color, ...state }: WidgetOptions) {
|
|
||||||
super()
|
|
||||||
this.state = state
|
|
||||||
this.color = color
|
|
||||||
}
|
|
||||||
|
|
||||||
eq(other: ColorPickerWidget) {
|
|
||||||
return (
|
|
||||||
other.state.colorType === this.state.colorType &&
|
|
||||||
other.color === this.color &&
|
|
||||||
other.state.from === this.state.from &&
|
|
||||||
other.state.to === this.state.to &&
|
|
||||||
other.state.alpha === this.state.alpha
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
toDOM() {
|
|
||||||
const picker = document.createElement('input')
|
|
||||||
pickerState.set(picker, this.state)
|
|
||||||
picker.type = 'color'
|
|
||||||
picker.value = this.color
|
|
||||||
|
|
||||||
const wrapper = document.createElement('span')
|
|
||||||
wrapper.appendChild(picker)
|
|
||||||
wrapper.className = wrapperClassName
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
ignoreEvent() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const colorPickerTheme = EditorView.baseTheme({
|
|
||||||
[`.${wrapperClassName}`]: {
|
|
||||||
display: 'inline-block',
|
|
||||||
outline: '1px solid #eee',
|
|
||||||
marginRight: '0.6ch',
|
|
||||||
height: '1em',
|
|
||||||
width: '1em',
|
|
||||||
transform: 'translateY(1px)',
|
|
||||||
},
|
|
||||||
[`.${wrapperClassName} input[type="color"]`]: {
|
|
||||||
cursor: 'pointer',
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
padding: 0,
|
|
||||||
border: 'none',
|
|
||||||
'&::-webkit-color-swatch-wrapper': {
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
'&::-webkit-color-swatch': {
|
|
||||||
border: 'none',
|
|
||||||
},
|
|
||||||
'&::-moz-color-swatch': {
|
|
||||||
border: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
interface IFactoryOptions {
|
|
||||||
discoverColors: typeof discoverColorsInKCL
|
|
||||||
}
|
|
||||||
|
|
||||||
export const makeColorPicker = (options: IFactoryOptions) =>
|
|
||||||
ViewPlugin.fromClass(
|
|
||||||
class ColorPickerViewPlugin {
|
|
||||||
decorations: DecorationSet
|
|
||||||
|
|
||||||
constructor(view: EditorView) {
|
|
||||||
this.decorations = colorPickersDecorations(view, options.discoverColors)
|
|
||||||
}
|
|
||||||
|
|
||||||
update(update: ViewUpdate) {
|
|
||||||
if (update.docChanged || update.viewportChanged) {
|
|
||||||
this.decorations = colorPickersDecorations(
|
|
||||||
update.view,
|
|
||||||
options.discoverColors
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
decorations: (v) => v.decorations,
|
|
||||||
eventHandlers: {
|
|
||||||
change: (e, view) => {
|
|
||||||
const target = e.target as HTMLInputElement
|
|
||||||
if (
|
|
||||||
target.nodeName !== 'INPUT' ||
|
|
||||||
!target.parentElement ||
|
|
||||||
!target.parentElement.classList.contains(wrapperClassName)
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = pickerState.get(target)!
|
|
||||||
|
|
||||||
let converted = '"' + target.value + data.alpha + '"'
|
|
||||||
|
|
||||||
view.dispatch({
|
|
||||||
changes: {
|
|
||||||
from: data.from,
|
|
||||||
to: data.to,
|
|
||||||
insert: converted,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export const colorPicker: Extension = [
|
|
||||||
makeColorPicker({ discoverColors: discoverColorsInKCL }),
|
|
||||||
colorPickerTheme,
|
|
||||||
]
|
|
||||||
@ -17,7 +17,6 @@ import { kclPlugin } from '.'
|
|||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
// @ts-ignore: No types available
|
// @ts-ignore: No types available
|
||||||
import { parser } from './kcl.grammar'
|
import { parser } from './kcl.grammar'
|
||||||
import { colorPicker } from './colors'
|
|
||||||
|
|
||||||
export interface LanguageOptions {
|
export interface LanguageOptions {
|
||||||
workspaceFolders: LSP.WorkspaceFolder[]
|
workspaceFolders: LSP.WorkspaceFolder[]
|
||||||
@ -55,14 +54,14 @@ export const KclLanguage = LRLanguage.define({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export function kcl(options: LanguageOptions) {
|
export function kcl(options: LanguageOptions) {
|
||||||
return new LanguageSupport(KclLanguage, [
|
return new LanguageSupport(
|
||||||
colorPicker,
|
KclLanguage,
|
||||||
kclPlugin({
|
kclPlugin({
|
||||||
documentUri: options.documentUri,
|
documentUri: options.documentUri,
|
||||||
workspaceFolders: options.workspaceFolders,
|
workspaceFolders: options.workspaceFolders,
|
||||||
allowHTMLContent: true,
|
allowHTMLContent: true,
|
||||||
client: options.client,
|
client: options.client,
|
||||||
processLspNotification: options.processLspNotification,
|
processLspNotification: options.processLspNotification,
|
||||||
}),
|
})
|
||||||
])
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -311,6 +311,8 @@ export class KclManager {
|
|||||||
// Do not send send scene commands if the program was interrupted, go to clean up
|
// Do not send send scene commands if the program was interrupted, go to clean up
|
||||||
if (!isInterrupted) {
|
if (!isInterrupted) {
|
||||||
this.addDiagnostics(await lintAst({ ast: ast }))
|
this.addDiagnostics(await lintAst({ ast: ast }))
|
||||||
|
|
||||||
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||||
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
||||||
|
|
||||||
if (args.zoomToFit) {
|
if (args.zoomToFit) {
|
||||||
@ -356,13 +358,7 @@ export class KclManager {
|
|||||||
this.lastSuccessfulProgramMemory = execState.memory
|
this.lastSuccessfulProgramMemory = execState.memory
|
||||||
}
|
}
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
// updateArtifactGraph relies on updated executeState/programMemory
|
|
||||||
await this.engineCommandManager.updateArtifactGraph(this.ast)
|
|
||||||
this._executeCallback()
|
this._executeCallback()
|
||||||
if (!isInterrupted) {
|
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
|
||||||
}
|
|
||||||
|
|
||||||
this.engineCommandManager.addCommandLog({
|
this.engineCommandManager.addCommandLog({
|
||||||
type: 'execution-done',
|
type: 'execution-done',
|
||||||
data: null,
|
data: null,
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export default class CodeManager {
|
|||||||
} else if (storedCode === null) {
|
} else if (storedCode === null) {
|
||||||
this.code = bracket
|
this.code = bracket
|
||||||
} else {
|
} else {
|
||||||
this.code = storedCode
|
this.code = storedCode || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +57,10 @@ export default class CodeManager {
|
|||||||
return this._code
|
return this._code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localStoragePersistCode(): string {
|
||||||
|
return safeLSGetItem(PERSIST_CODE_KEY) || ''
|
||||||
|
}
|
||||||
|
|
||||||
registerCallBacks({ setCode }: { setCode: (arg: string) => void }) {
|
registerCallBacks({ setCode }: { setCode: (arg: string) => void }) {
|
||||||
this.#updateState = setCode
|
this.#updateState = setCode
|
||||||
}
|
}
|
||||||
@ -165,7 +169,7 @@ export default class CodeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function safeLSGetItem(key: string) {
|
function safeLSGetItem(key: string) {
|
||||||
if (typeof window === 'undefined') return null
|
if (typeof window === 'undefined') return
|
||||||
return localStorage?.getItem(key)
|
return localStorage?.getItem(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -66,7 +66,9 @@ export async function executeAst({
|
|||||||
? enginelessExecutor(ast, programMemoryOverride)
|
? enginelessExecutor(ast, programMemoryOverride)
|
||||||
: _executor(ast, engineCommandManager))
|
: _executor(ast, engineCommandManager))
|
||||||
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands(
|
||||||
|
programMemoryOverride !== undefined
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logs: [],
|
logs: [],
|
||||||
|
|||||||
@ -247,7 +247,7 @@ extrude003 = extrude(-15, sketch003)`
|
|||||||
selectedSegmentSnippet,
|
selectedSegmentSnippet,
|
||||||
expectedExtrudeSnippet
|
expectedExtrudeSnippet
|
||||||
)
|
)
|
||||||
}, 5_000)
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
||||||
@ -477,7 +477,7 @@ extrude001 = extrude(-15, sketch001)
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(-15, sketch001)
|
extrude001 = extrude(-15, sketch001)
|
||||||
|> chamfer({ length = 5, tags = [seg01] }, %)`
|
|> chamfer({ length: 5, tags: [seg01] }, %)`
|
||||||
const segmentSnippets = ['line([-20, 0], %)']
|
const segmentSnippets = ['line([-20, 0], %)']
|
||||||
const expectedCode = `sketch001 = startSketchOn('XY')
|
const expectedCode = `sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, 10], %)
|
|> startProfileAt([-10, 10], %)
|
||||||
@ -487,8 +487,8 @@ extrude001 = extrude(-15, sketch001)
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(-15, sketch001)
|
extrude001 = extrude(-15, sketch001)
|
||||||
|> chamfer({ length = 5, tags = [seg01] }, %)
|
|> chamfer({ length: 5, tags: [seg01] }, %)
|
||||||
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
|
|> ${edgeTreatmentType}({ ${parameterName}: 3, tags: [seg02] }, %)`
|
||||||
|
|
||||||
await runModifyAstCloneWithEdgeTreatmentAndTag(
|
await runModifyAstCloneWithEdgeTreatmentAndTag(
|
||||||
code,
|
code,
|
||||||
|
|||||||
@ -13,7 +13,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
12,
|
12,
|
||||||
31,
|
31,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
@ -33,7 +33,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
37,
|
37,
|
||||||
64,
|
64,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
@ -60,7 +60,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
70,
|
70,
|
||||||
86,
|
86,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -83,7 +83,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
92,
|
92,
|
||||||
119,
|
119,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeCutId": "UUID",
|
"edgeCutId": "UUID",
|
||||||
@ -107,7 +107,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
125,
|
125,
|
||||||
150,
|
150,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -130,7 +130,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
156,
|
156,
|
||||||
203,
|
203,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -153,7 +153,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
209,
|
209,
|
||||||
217,
|
217,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [],
|
"edgeIds": [],
|
||||||
@ -177,7 +177,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
231,
|
231,
|
||||||
254,
|
254,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -320,7 +320,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
260,
|
260,
|
||||||
299,
|
299,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"consumedEdgeId": "UUID",
|
"consumedEdgeId": "UUID",
|
||||||
@ -340,7 +340,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
350,
|
350,
|
||||||
377,
|
377,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
@ -366,7 +366,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
383,
|
383,
|
||||||
398,
|
398,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -389,7 +389,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
404,
|
404,
|
||||||
420,
|
420,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -412,7 +412,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
426,
|
426,
|
||||||
473,
|
473,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -435,7 +435,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
479,
|
479,
|
||||||
487,
|
487,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [],
|
"edgeIds": [],
|
||||||
@ -459,7 +459,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
501,
|
501,
|
||||||
522,
|
522,
|
||||||
true,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -478,6 +478,7 @@ Map {
|
|||||||
"UUID",
|
"UUID",
|
||||||
"UUID",
|
"UUID",
|
||||||
"UUID",
|
"UUID",
|
||||||
|
"UUID",
|
||||||
],
|
],
|
||||||
"type": "sweep",
|
"type": "sweep",
|
||||||
},
|
},
|
||||||
@ -509,46 +510,54 @@ Map {
|
|||||||
"edgeCutEdgeIds": [],
|
"edgeCutEdgeIds": [],
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"pathIds": [],
|
"pathIds": [],
|
||||||
"subType": "end",
|
"subType": "start",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "cap",
|
"type": "cap",
|
||||||
},
|
},
|
||||||
"UUID-35" => {
|
"UUID-35" => {
|
||||||
|
"edgeCutEdgeIds": [],
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"pathIds": [],
|
||||||
"subType": "opposite",
|
"subType": "end",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "sweepEdge",
|
"type": "cap",
|
||||||
},
|
},
|
||||||
"UUID-36" => {
|
"UUID-36" => {
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "adjacent",
|
"subType": "opposite",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "sweepEdge",
|
"type": "sweepEdge",
|
||||||
},
|
},
|
||||||
"UUID-37" => {
|
"UUID-37" => {
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "opposite",
|
"subType": "adjacent",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "sweepEdge",
|
"type": "sweepEdge",
|
||||||
},
|
},
|
||||||
"UUID-38" => {
|
"UUID-38" => {
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "adjacent",
|
"subType": "opposite",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "sweepEdge",
|
"type": "sweepEdge",
|
||||||
},
|
},
|
||||||
"UUID-39" => {
|
"UUID-39" => {
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "opposite",
|
"subType": "adjacent",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "sweepEdge",
|
"type": "sweepEdge",
|
||||||
},
|
},
|
||||||
"UUID-40" => {
|
"UUID-40" => {
|
||||||
|
"id": "UUID",
|
||||||
|
"segId": "UUID",
|
||||||
|
"subType": "opposite",
|
||||||
|
"sweepId": "UUID",
|
||||||
|
"type": "sweepEdge",
|
||||||
|
},
|
||||||
|
"UUID-41" => {
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "adjacent",
|
"subType": "adjacent",
|
||||||
|
|||||||
@ -259,13 +259,11 @@ describe('testing createArtifactGraph', () => {
|
|||||||
if (err(extrusion)) throw extrusion
|
if (err(extrusion)) throw extrusion
|
||||||
expect(extrusion.type).toBe('sweep')
|
expect(extrusion.type).toBe('sweep')
|
||||||
const firstExtrusionIsACubeIE6Sides = 6
|
const firstExtrusionIsACubeIE6Sides = 6
|
||||||
// Each face of the triangular prism (5), but without the bottom cap.
|
const secondExtrusionIsATriangularPrismIE5Sides = 5
|
||||||
// The engine doesn't generate that.
|
|
||||||
const secondExtrusionIsATriangularPrism = 4
|
|
||||||
expect(extrusion.surfaces.length).toBe(
|
expect(extrusion.surfaces.length).toBe(
|
||||||
!index
|
!index
|
||||||
? firstExtrusionIsACubeIE6Sides
|
? firstExtrusionIsACubeIE6Sides
|
||||||
: secondExtrusionIsATriangularPrism
|
: secondExtrusionIsATriangularPrismIE5Sides
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -661,7 +659,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
sweepId: '',
|
sweepId: '',
|
||||||
codeRef: {
|
codeRef: {
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
range: [37, 64, true],
|
range: [37, 64, 0],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@ -674,7 +672,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: [],
|
surfaceIds: [],
|
||||||
edgeIds: [],
|
edgeIds: [],
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, true],
|
range: [231, 254, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -685,7 +683,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
planeId: expect.any(String),
|
planeId: expect.any(String),
|
||||||
sweepId: expect.any(String),
|
sweepId: expect.any(String),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [37, 64, true],
|
range: [37, 64, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
solid2dId: expect.any(String),
|
solid2dId: expect.any(String),
|
||||||
@ -699,7 +697,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: '',
|
surfaceId: '',
|
||||||
edgeIds: [],
|
edgeIds: [],
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [70, 86, true],
|
range: [70, 86, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -710,7 +708,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
planeId: expect.any(String),
|
planeId: expect.any(String),
|
||||||
sweepId: expect.any(String),
|
sweepId: expect.any(String),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [37, 64, true],
|
range: [37, 64, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
solid2dId: expect.any(String),
|
solid2dId: expect.any(String),
|
||||||
@ -725,7 +723,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
edgeIds: [],
|
edgeIds: [],
|
||||||
surfaceId: '',
|
surfaceId: '',
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [260, 299, true],
|
range: [260, 299, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -736,7 +734,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [92, 119, true],
|
range: [92, 119, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
edgeCutId: expect.any(String),
|
edgeCutId: expect.any(String),
|
||||||
@ -758,7 +756,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [156, 203, true],
|
range: [156, 203, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -770,7 +768,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, true],
|
range: [231, 254, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -789,7 +787,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [125, 150, true],
|
range: [125, 150, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -801,7 +799,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, true],
|
range: [231, 254, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -820,7 +818,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [92, 119, true],
|
range: [92, 119, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
edgeCutId: expect.any(String),
|
edgeCutId: expect.any(String),
|
||||||
@ -833,7 +831,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, true],
|
range: [231, 254, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -852,7 +850,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [70, 86, true],
|
range: [70, 86, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -864,7 +862,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, true],
|
range: [231, 254, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -884,7 +882,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, true],
|
range: [231, 254, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -904,7 +902,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, true],
|
range: [231, 254, 0],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -871,3 +871,15 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
|
|||||||
pathToNode: getNodePathFromSourceRange(ast, range),
|
pathToNode: getNodePathFromSourceRange(ast, range),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSolid2D(artifact: Artifact): artifact is solid2D {
|
||||||
|
return (artifact as solid2D).pathId !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSegment(artifact: Artifact): artifact is SegmentArtifact {
|
||||||
|
return (artifact as SegmentArtifact).pathId !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSweep(artifact: Artifact): artifact is SweepArtifact {
|
||||||
|
return (artifact as SweepArtifact).pathId !== undefined
|
||||||
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 357 KiB |
|
Before Width: | Height: | Size: 568 KiB After Width: | Height: | Size: 577 KiB |
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { defaultSourceRange, SourceRange } from 'lang/wasm'
|
||||||
defaultRustSourceRange,
|
|
||||||
defaultSourceRange,
|
|
||||||
Program,
|
|
||||||
RustSourceRange,
|
|
||||||
SourceRange,
|
|
||||||
sourceRangeFromRust,
|
|
||||||
} from 'lang/wasm'
|
|
||||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
@ -1309,8 +1302,8 @@ export enum EngineCommandManagerEvents {
|
|||||||
|
|
||||||
interface PendingMessage {
|
interface PendingMessage {
|
||||||
command: EngineCommand
|
command: EngineCommand
|
||||||
range: RustSourceRange
|
range: SourceRange
|
||||||
idToRangeMap: { [key: string]: RustSourceRange }
|
idToRangeMap: { [key: string]: SourceRange }
|
||||||
resolve: (data: [Models['WebSocketResponse_type']]) => void
|
resolve: (data: [Models['WebSocketResponse_type']]) => void
|
||||||
reject: (reason: string) => void
|
reject: (reason: string) => void
|
||||||
promise: Promise<[Models['WebSocketResponse_type']]>
|
promise: Promise<[Models['WebSocketResponse_type']]>
|
||||||
@ -2000,7 +1993,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
{
|
{
|
||||||
command,
|
command,
|
||||||
idToRangeMap: {},
|
idToRangeMap: {},
|
||||||
range: defaultRustSourceRange(),
|
range: defaultSourceRange(),
|
||||||
},
|
},
|
||||||
true // isSceneCommand
|
true // isSceneCommand
|
||||||
)
|
)
|
||||||
@ -2031,9 +2024,9 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
return Promise.reject(new Error('rangeStr is undefined'))
|
return Promise.reject(new Error('rangeStr is undefined'))
|
||||||
if (commandStr === undefined)
|
if (commandStr === undefined)
|
||||||
return Promise.reject(new Error('commandStr is undefined'))
|
return Promise.reject(new Error('commandStr is undefined'))
|
||||||
const range: RustSourceRange = JSON.parse(rangeStr)
|
const range: SourceRange = JSON.parse(rangeStr)
|
||||||
const command: EngineCommand = JSON.parse(commandStr)
|
const command: EngineCommand = JSON.parse(commandStr)
|
||||||
const idToRangeMap: { [key: string]: RustSourceRange } =
|
const idToRangeMap: { [key: string]: SourceRange } =
|
||||||
JSON.parse(idToRangeStr)
|
JSON.parse(idToRangeStr)
|
||||||
|
|
||||||
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
|
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
|
||||||
@ -2076,14 +2069,10 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
if (message.command.type === 'modeling_cmd_req') {
|
if (message.command.type === 'modeling_cmd_req') {
|
||||||
this.orderedCommands.push({
|
this.orderedCommands.push({
|
||||||
command: message.command,
|
command: message.command,
|
||||||
range: sourceRangeFromRust(message.range),
|
range: message.range,
|
||||||
})
|
})
|
||||||
} else if (message.command.type === 'modeling_cmd_batch_req') {
|
} else if (message.command.type === 'modeling_cmd_batch_req') {
|
||||||
message.command.requests.forEach((req) => {
|
message.command.requests.forEach((req) => {
|
||||||
const cmdId = req.cmd_id || ''
|
|
||||||
const range = cmdId
|
|
||||||
? sourceRangeFromRust(message.idToRangeMap[cmdId])
|
|
||||||
: defaultSourceRange()
|
|
||||||
const cmd: EngineCommand = {
|
const cmd: EngineCommand = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: req.cmd_id,
|
cmd_id: req.cmd_id,
|
||||||
@ -2091,7 +2080,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
this.orderedCommands.push({
|
this.orderedCommands.push({
|
||||||
command: cmd,
|
command: cmd,
|
||||||
range,
|
range: message.idToRangeMap[req.cmd_id || ''],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -2110,23 +2099,30 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
* When an execution takes place we want to wait until we've got replies for all of the commands
|
* When an execution takes place we want to wait until we've got replies for all of the commands
|
||||||
* When this is done when we build the artifact map synchronously.
|
* When this is done when we build the artifact map synchronously.
|
||||||
*/
|
*/
|
||||||
waitForAllCommands() {
|
async waitForAllCommands(useFakeExecutor = false) {
|
||||||
return Promise.all(
|
await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise))
|
||||||
Object.values(this.pendingCommands).map((a) => a.promise)
|
setTimeout(() => {
|
||||||
)
|
// the ast is wrong without this one tick timeout.
|
||||||
}
|
// an example is `Solids should be select and deletable` e2e test will fail
|
||||||
updateArtifactGraph(ast: Program) {
|
// because the out of date ast messes with selections
|
||||||
this.artifactGraph = createArtifactGraph({
|
// TODO: race condition
|
||||||
orderedCommands: this.orderedCommands,
|
if (!this?.kclManager) return
|
||||||
responseMap: this.responseMap,
|
this.artifactGraph = createArtifactGraph({
|
||||||
ast,
|
orderedCommands: this.orderedCommands,
|
||||||
|
responseMap: this.responseMap,
|
||||||
|
ast: this.kclManager.ast,
|
||||||
|
})
|
||||||
|
if (useFakeExecutor) {
|
||||||
|
// mock executions don't produce an artifactGraph, so this will always be empty
|
||||||
|
// skipping the below logic to wait for the next real execution
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.artifactGraph.size) {
|
||||||
|
this.deferredArtifactEmptied(null)
|
||||||
|
} else {
|
||||||
|
this.deferredArtifactPopulated(null)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// TODO check if these still need to be deferred once e2e tests are working again.
|
|
||||||
if (this.artifactGraph.size) {
|
|
||||||
this.deferredArtifactEmptied(null)
|
|
||||||
} else {
|
|
||||||
this.deferredArtifactPopulated(null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -65,7 +65,6 @@ export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
|
|||||||
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
||||||
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
|
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
|
||||||
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
||||||
export type { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
|
||||||
|
|
||||||
export type SyntaxType =
|
export type SyntaxType =
|
||||||
| 'Program'
|
| 'Program'
|
||||||
@ -118,13 +117,6 @@ export function defaultSourceRange(): SourceRange {
|
|||||||
return [0, 0, true]
|
return [0, 0, true]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a default RustSourceRange for testing or as a placeholder.
|
|
||||||
*/
|
|
||||||
export function defaultRustSourceRange(): RustSourceRange {
|
|
||||||
return [0, 0, 0]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const wasmUrl = () => {
|
export const wasmUrl = () => {
|
||||||
// For when we're in electron (file based) or web server (network based)
|
// For when we're in electron (file based) or web server (network based)
|
||||||
// For some reason relative paths don't work as expected. Otherwise we would
|
// For some reason relative paths don't work as expected. Otherwise we would
|
||||||
|
|||||||
@ -10,7 +10,7 @@ const noModifiersPressed = (e: MouseEvent) =>
|
|||||||
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
|
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
|
||||||
|
|
||||||
export type CameraSystem =
|
export type CameraSystem =
|
||||||
| 'Zoo'
|
| 'KittyCAD'
|
||||||
| 'OnShape'
|
| 'OnShape'
|
||||||
| 'Trackpad Friendly'
|
| 'Trackpad Friendly'
|
||||||
| 'Solidworks'
|
| 'Solidworks'
|
||||||
@ -19,7 +19,7 @@ export type CameraSystem =
|
|||||||
| 'AutoCAD'
|
| 'AutoCAD'
|
||||||
|
|
||||||
export const cameraSystems: CameraSystem[] = [
|
export const cameraSystems: CameraSystem[] = [
|
||||||
'Zoo',
|
'KittyCAD',
|
||||||
'OnShape',
|
'OnShape',
|
||||||
'Trackpad Friendly',
|
'Trackpad Friendly',
|
||||||
'Solidworks',
|
'Solidworks',
|
||||||
@ -34,8 +34,9 @@ export function mouseControlsToCameraSystem(
|
|||||||
switch (mouseControl) {
|
switch (mouseControl) {
|
||||||
// TODO: understand why the values come back without underscores and fix the root cause
|
// TODO: understand why the values come back without underscores and fix the root cause
|
||||||
// @ts-ignore: TS2678
|
// @ts-ignore: TS2678
|
||||||
case 'zoo':
|
case 'kittycad':
|
||||||
return 'Zoo'
|
case 'kitty_cad':
|
||||||
|
return 'KittyCAD'
|
||||||
// TODO: understand why the values come back without underscores and fix the root cause
|
// TODO: understand why the values come back without underscores and fix the root cause
|
||||||
// @ts-ignore: TS2678
|
// @ts-ignore: TS2678
|
||||||
case 'onshape':
|
case 'onshape':
|
||||||
@ -85,7 +86,7 @@ export const btnName = (e: MouseEvent) => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||||
Zoo: {
|
KittyCAD: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Shift + Right click drag or middle click drag',
|
description: 'Shift + Right click drag or middle click drag',
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { engineCommandManager } from 'lib/singletons'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { CommandBarContext } from 'machines/commandBarMachine'
|
import { CommandBarContext } from 'machines/commandBarMachine'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
|
import { isSolid2D, isSegment, isSweep } from 'lang/std/artifactGraph'
|
||||||
|
|
||||||
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
||||||
for (let tries = 0; tries < numberOfRetries; tries++) {
|
for (let tries = 0; tries < numberOfRetries; tries++) {
|
||||||
@ -63,7 +64,7 @@ export const revolveAxisValidator = async ({
|
|||||||
return 'Unable to revolve, sketch not found'
|
return 'Unable to revolve, sketch not found'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!('pathId' in artifact)) {
|
if (!(isSolid2D(artifact) || isSegment(artifact) || isSweep(artifact))) {
|
||||||
return 'Unable to revolve, sketch has no path'
|
return 'Unable to revolve, sketch has no path'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -118,21 +118,3 @@ export const KCL_AXIS_Y = 'Y'
|
|||||||
export const KCL_AXIS_NEG_X = '-X'
|
export const KCL_AXIS_NEG_X = '-X'
|
||||||
export const KCL_AXIS_NEG_Y = '-Y'
|
export const KCL_AXIS_NEG_Y = '-Y'
|
||||||
export const KCL_DEFAULT_AXIS = 'X'
|
export const KCL_DEFAULT_AXIS = 'X'
|
||||||
|
|
||||||
export enum AxisNames {
|
|
||||||
X = 'x',
|
|
||||||
Y = 'y',
|
|
||||||
Z = 'z',
|
|
||||||
NEG_X = '-x',
|
|
||||||
NEG_Y = '-y',
|
|
||||||
NEG_Z = '-z',
|
|
||||||
}
|
|
||||||
/** Semantic names of views from AxisNames */
|
|
||||||
export const VIEW_NAMES_SEMANTIC = {
|
|
||||||
[AxisNames.X]: 'Right',
|
|
||||||
[AxisNames.Y]: 'Back',
|
|
||||||
[AxisNames.Z]: 'Top',
|
|
||||||
[AxisNames.NEG_X]: 'Left',
|
|
||||||
[AxisNames.NEG_Y]: 'Front',
|
|
||||||
[AxisNames.NEG_Z]: 'Bottom',
|
|
||||||
} as const
|
|
||||||
|
|||||||
@ -391,7 +391,9 @@ const getAppFolderName = () => {
|
|||||||
|
|
||||||
export const getAppSettingsFilePath = async () => {
|
export const getAppSettingsFilePath = async () => {
|
||||||
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
|
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
|
||||||
const testSettingsPath = window.electron.process.env.TEST_SETTINGS_FILE_KEY
|
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||||
|
'TEST_SETTINGS_FILE_KEY'
|
||||||
|
)
|
||||||
const appConfig = await window.electron.getPath('appData')
|
const appConfig = await window.electron.getPath('appData')
|
||||||
const fullPath = isTestEnv
|
const fullPath = isTestEnv
|
||||||
? testSettingsPath
|
? testSettingsPath
|
||||||
@ -408,7 +410,9 @@ export const getAppSettingsFilePath = async () => {
|
|||||||
}
|
}
|
||||||
const getTokenFilePath = async () => {
|
const getTokenFilePath = async () => {
|
||||||
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
|
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
|
||||||
const testSettingsPath = window.electron.process.env.TEST_SETTINGS_FILE_KEY
|
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||||
|
'TEST_SETTINGS_FILE_KEY'
|
||||||
|
)
|
||||||
const appConfig = await window.electron.getPath('appData')
|
const appConfig = await window.electron.getPath('appData')
|
||||||
const fullPath = isTestEnv
|
const fullPath = isTestEnv
|
||||||
? testSettingsPath
|
? testSettingsPath
|
||||||
|
|||||||
@ -17,9 +17,17 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (window.electron.process.env.IS_PLAYWRIGHT) {
|
if (window.electron.process.env.IS_PLAYWRIGHT) {
|
||||||
// skip file picker, save to default location
|
// Skip file picker, save to the test dir downloads directory
|
||||||
|
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||||
|
'TEST_SETTINGS_FILE_KEY'
|
||||||
|
)
|
||||||
|
const downloadDir = window.electron.join(
|
||||||
|
testSettingsPath,
|
||||||
|
'downloads-during-playwright'
|
||||||
|
)
|
||||||
|
await window.electron.mkdir(downloadDir, { recursive: true })
|
||||||
await window.electron.writeFile(
|
await window.electron.writeFile(
|
||||||
file.name,
|
window.electron.join(downloadDir, file.name),
|
||||||
new Uint8Array(file.contents)
|
new Uint8Array(file.contents)
|
||||||
)
|
)
|
||||||
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })
|
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { fileSystemManager } from 'lang/std/fileSystemManager'
|
|||||||
import { getProjectInfo } from './desktop'
|
import { getProjectInfo } from './desktop'
|
||||||
import { createSettings } from './settings/initialSettings'
|
import { createSettings } from './settings/initialSettings'
|
||||||
import { normalizeLineEndings } from 'lib/codeEditor'
|
import { normalizeLineEndings } from 'lib/codeEditor'
|
||||||
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
|
|
||||||
|
|
||||||
// The root loader simply resolves the settings and any errors that
|
// The root loader simply resolves the settings and any errors that
|
||||||
// occurred during the settings load
|
// occurred during the settings load
|
||||||
@ -54,15 +53,14 @@ export const telemetryLoader: LoaderFunction = async ({
|
|||||||
// Redirect users to the appropriate onboarding page if they haven't completed it
|
// Redirect users to the appropriate onboarding page if they haven't completed it
|
||||||
export const onboardingRedirectLoader: ActionFunction = async (args) => {
|
export const onboardingRedirectLoader: ActionFunction = async (args) => {
|
||||||
const { settings } = await loadAndValidateSettings()
|
const { settings } = await loadAndValidateSettings()
|
||||||
const onboardingStatus: OnboardingStatus =
|
const onboardingStatus = settings.app.onboardingStatus.current || ''
|
||||||
settings.app.onboardingStatus.current || ''
|
|
||||||
const notEnRouteToOnboarding = !args.request.url.includes(
|
const notEnRouteToOnboarding = !args.request.url.includes(
|
||||||
PATHS.ONBOARDING.INDEX
|
PATHS.ONBOARDING.INDEX
|
||||||
)
|
)
|
||||||
// '' is the initial state, 'completed' and 'dismissed' are the final states
|
// '' is the initial state, 'done' and 'dismissed' are the final states
|
||||||
const hasValidOnboardingStatus =
|
const hasValidOnboardingStatus =
|
||||||
onboardingStatus.length === 0 ||
|
onboardingStatus.length === 0 ||
|
||||||
!(onboardingStatus === 'completed' || onboardingStatus === 'dismissed')
|
!(onboardingStatus === 'done' || onboardingStatus === 'dismissed')
|
||||||
const shouldRedirectToOnboarding =
|
const shouldRedirectToOnboarding =
|
||||||
notEnRouteToOnboarding && hasValidOnboardingStatus
|
notEnRouteToOnboarding && hasValidOnboardingStatus
|
||||||
|
|
||||||
@ -87,12 +85,13 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
)
|
)
|
||||||
const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH)
|
const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH)
|
||||||
|
|
||||||
|
let code = ''
|
||||||
|
|
||||||
if (!isBrowserProject && projectPathData) {
|
if (!isBrowserProject && projectPathData) {
|
||||||
const { projectName, projectPath, currentFileName, currentFilePath } =
|
const { projectName, projectPath, currentFileName, currentFilePath } =
|
||||||
projectPathData
|
projectPathData
|
||||||
|
|
||||||
const urlObj = new URL(routerData.request.url)
|
const urlObj = new URL(routerData.request.url)
|
||||||
let code = ''
|
|
||||||
|
|
||||||
if (!urlObj.pathname.endsWith('/settings')) {
|
if (!urlObj.pathname.endsWith('/settings')) {
|
||||||
const fallbackFile = isDesktop()
|
const fallbackFile = isDesktop()
|
||||||
@ -122,6 +121,10 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
})
|
})
|
||||||
code = normalizeLineEndings(code)
|
code = normalizeLineEndings(code)
|
||||||
|
|
||||||
|
// If persistCode in localStorage is present, it'll persist that code
|
||||||
|
// through *anything*. INTENDED FOR TESTS.
|
||||||
|
code = codeManager.localStoragePersistCode() || code
|
||||||
|
|
||||||
// Update both the state and the editor's code.
|
// Update both the state and the editor's code.
|
||||||
// We explicitly do not write to the file here since we are loading from
|
// We explicitly do not write to the file here since we are loading from
|
||||||
// the file system and not the editor.
|
// the file system and not the editor.
|
||||||
@ -149,12 +152,6 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
? await getProjectInfo(projectPath)
|
? await getProjectInfo(projectPath)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
console.log('maybeProjectInfo', {
|
|
||||||
maybeProjectInfo,
|
|
||||||
defaultProjectData,
|
|
||||||
projectPathData,
|
|
||||||
})
|
|
||||||
|
|
||||||
const projectData: IndexLoaderData = {
|
const projectData: IndexLoaderData = {
|
||||||
code,
|
code,
|
||||||
project: maybeProjectInfo ?? defaultProjectData,
|
project: maybeProjectInfo ?? defaultProjectData,
|
||||||
@ -171,7 +168,7 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: '',
|
code,
|
||||||
project: {
|
project: {
|
||||||
name: BROWSER_PROJECT_NAME,
|
name: BROWSER_PROJECT_NAME,
|
||||||
path: '/' + BROWSER_PROJECT_NAME,
|
path: '/' + BROWSER_PROJECT_NAME,
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import Tooltip from 'components/Tooltip'
|
|||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
||||||
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A setting that can be set at the user or project level
|
* A setting that can be set at the user or project level
|
||||||
@ -190,10 +189,8 @@ export function createSettings() {
|
|||||||
inputType: 'boolean',
|
inputType: 'boolean',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
onboardingStatus: new Setting<OnboardingStatus>({
|
onboardingStatus: new Setting<string>({
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
// TODO: this could be better but we don't have a TS side real enum
|
|
||||||
// for this yet
|
|
||||||
validate: (v) => typeof v === 'string',
|
validate: (v) => typeof v === 'string',
|
||||||
hideOnPlatform: 'both',
|
hideOnPlatform: 'both',
|
||||||
}),
|
}),
|
||||||
@ -286,7 +283,7 @@ export function createSettings() {
|
|||||||
* The controls for how to navigate the 3D view
|
* The controls for how to navigate the 3D view
|
||||||
*/
|
*/
|
||||||
mouseControls: new Setting<CameraSystem>({
|
mouseControls: new Setting<CameraSystem>({
|
||||||
defaultValue: 'Zoo',
|
defaultValue: 'KittyCAD',
|
||||||
description: 'The controls for how to navigate the 3D view',
|
description: 'The controls for how to navigate the 3D view',
|
||||||
validate: (v) => cameraSystems.includes(v as CameraSystem),
|
validate: (v) => cameraSystems.includes(v as CameraSystem),
|
||||||
hideOnLevel: 'project',
|
hideOnLevel: 'project',
|
||||||
|
|||||||
59
src/main.ts
@ -61,23 +61,27 @@ if (process.defaultApp) {
|
|||||||
// Must be done before ready event.
|
// Must be done before ready event.
|
||||||
registerStartupListeners()
|
registerStartupListeners()
|
||||||
|
|
||||||
const createWindow = (filePath?: string): BrowserWindow => {
|
const createWindow = (filePath?: string, reuse?: boolean): BrowserWindow => {
|
||||||
const newWindow = new BrowserWindow({
|
const newWindow = reuse
|
||||||
autoHideMenuBar: true,
|
? mainWindow
|
||||||
show: false,
|
: new BrowserWindow({
|
||||||
width: 1800,
|
autoHideMenuBar: true,
|
||||||
height: 1200,
|
show: false,
|
||||||
webPreferences: {
|
width: 1800,
|
||||||
nodeIntegration: false, // do not give the application implicit system access
|
height: 1200,
|
||||||
contextIsolation: true, // expose system functions in preload
|
webPreferences: {
|
||||||
sandbox: false, // expose nodejs in preload
|
nodeIntegration: false, // do not give the application implicit system access
|
||||||
preload: path.join(__dirname, './preload.js'),
|
contextIsolation: true, // expose system functions in preload
|
||||||
},
|
sandbox: false, // expose nodejs in preload
|
||||||
icon: path.resolve(process.cwd(), 'assets', 'icon.png'),
|
preload: path.join(__dirname, './preload.js'),
|
||||||
frame: os.platform() !== 'darwin',
|
},
|
||||||
titleBarStyle: 'hiddenInset',
|
icon: path.resolve(process.cwd(), 'assets', 'icon.png'),
|
||||||
backgroundColor: nativeTheme.shouldUseDarkColors ? '#1C1C1C' : '#FCFCFC',
|
frame: os.platform() !== 'darwin',
|
||||||
})
|
titleBarStyle: 'hiddenInset',
|
||||||
|
backgroundColor: nativeTheme.shouldUseDarkColors
|
||||||
|
? '#1C1C1C'
|
||||||
|
: '#FCFCFC',
|
||||||
|
})
|
||||||
|
|
||||||
// and load the index.html of the app.
|
// and load the index.html of the app.
|
||||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||||
@ -110,7 +114,9 @@ const createWindow = (filePath?: string): BrowserWindow => {
|
|||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
// mainWindow.webContents.openDevTools()
|
// mainWindow.webContents.openDevTools()
|
||||||
|
|
||||||
newWindow.show()
|
if (!reuse) {
|
||||||
|
newWindow.show()
|
||||||
|
}
|
||||||
|
|
||||||
return newWindow
|
return newWindow
|
||||||
}
|
}
|
||||||
@ -134,6 +140,23 @@ app.on('ready', (event, data) => {
|
|||||||
// There is just not enough code to warrant it and further abstracts everything
|
// There is just not enough code to warrant it and further abstracts everything
|
||||||
// which is already quite abstracted
|
// which is already quite abstracted
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
// electron/electron.d.ts has done type = App, making declaration merging not
|
||||||
|
// possible :(
|
||||||
|
app.resizeWindow = async (width: number, height: number) => {
|
||||||
|
return mainWindow?.setSize(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.testProperty = {}
|
||||||
|
|
||||||
|
ipcMain.handle('app.testProperty', (event, propertyName) => {
|
||||||
|
return app.testProperty[propertyName]
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('app.resizeWindow', (event, data) => {
|
||||||
|
return mainWindow?.setSize(data[0], data[1])
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle('app.getPath', (event, data) => {
|
ipcMain.handle('app.getPath', (event, data) => {
|
||||||
return app.getPath(data)
|
return app.getPath(data)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import packageJson from '../package.json'
|
|||||||
import { MachinesListing } from 'components/MachineManagerProvider'
|
import { MachinesListing } from 'components/MachineManagerProvider'
|
||||||
import chokidar from 'chokidar'
|
import chokidar from 'chokidar'
|
||||||
|
|
||||||
|
const resizeWindow = (width: number, height: number) =>
|
||||||
|
ipcRenderer.invoke('app.resizeWindow', [width, height])
|
||||||
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
|
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
|
||||||
const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args)
|
const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args)
|
||||||
const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url)
|
const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url)
|
||||||
@ -18,13 +20,19 @@ const loginWithDeviceFlow = (): Promise<string> =>
|
|||||||
ipcRenderer.invoke('loginWithDeviceFlow')
|
ipcRenderer.invoke('loginWithDeviceFlow')
|
||||||
const onUpdateDownloaded = (
|
const onUpdateDownloaded = (
|
||||||
callback: (value: { version: string; releaseNotes: string }) => void
|
callback: (value: { version: string; releaseNotes: string }) => void
|
||||||
) => ipcRenderer.on('update-downloaded', (_event, value) => callback(value))
|
) =>
|
||||||
|
ipcRenderer.on('update-downloaded', (_event: any, value) => callback(value))
|
||||||
const onUpdateDownloadStart = (
|
const onUpdateDownloadStart = (
|
||||||
callback: (value: { version: string }) => void
|
callback: (value: { version: string }) => void
|
||||||
) => ipcRenderer.on('update-download-start', (_event, value) => callback(value))
|
) =>
|
||||||
|
ipcRenderer.on('update-download-start', (_event: any, value) =>
|
||||||
|
callback(value)
|
||||||
|
)
|
||||||
const onUpdateError = (callback: (value: Error) => void) =>
|
const onUpdateError = (callback: (value: Error) => void) =>
|
||||||
ipcRenderer.on('update-error', (_event, value) => callback(value))
|
ipcRenderer.on('update-error', (_event: any, value) => callback(value))
|
||||||
const appRestart = () => ipcRenderer.invoke('app.restart')
|
const appRestart = () => ipcRenderer.invoke('app.restart')
|
||||||
|
const getAppTestProperty = (propertyName: string) =>
|
||||||
|
ipcRenderer.invoke('app.testProperty', propertyName)
|
||||||
|
|
||||||
const isMac = os.platform() === 'darwin'
|
const isMac = os.platform() === 'darwin'
|
||||||
const isWindows = os.platform() === 'win32'
|
const isWindows = os.platform() === 'win32'
|
||||||
@ -157,14 +165,15 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
isWindows,
|
isWindows,
|
||||||
isLinux,
|
isLinux,
|
||||||
},
|
},
|
||||||
|
// Use this to access dynamic properties from the node side.
|
||||||
|
// INTENDED ONLY TO BE USED FOR TESTS.
|
||||||
|
getAppTestProperty,
|
||||||
process: {
|
process: {
|
||||||
// Setter/getter has to be created because
|
// These are read-only over the boundary.
|
||||||
// these are read-only over the boundary.
|
|
||||||
env: Object.assign(
|
env: Object.assign(
|
||||||
{},
|
{},
|
||||||
exposeProcessEnvs([
|
exposeProcessEnvs([
|
||||||
'NODE_ENV',
|
'NODE_ENV',
|
||||||
'TEST_SETTINGS_FILE_KEY',
|
|
||||||
'VITE_KC_API_WS_MODELING_URL',
|
'VITE_KC_API_WS_MODELING_URL',
|
||||||
'VITE_KC_API_BASE_URL',
|
'VITE_KC_API_BASE_URL',
|
||||||
'VITE_KC_SITE_BASE_URL',
|
'VITE_KC_SITE_BASE_URL',
|
||||||
@ -189,4 +198,5 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
onUpdateError,
|
onUpdateError,
|
||||||
appRestart,
|
appRestart,
|
||||||
getArgvParsed,
|
getArgvParsed,
|
||||||
|
resizeWindow,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
|
export const onboardingPaths = {
|
||||||
|
|
||||||
export const onboardingPaths: Record<string, OnboardingStatus> = {
|
|
||||||
INDEX: '/',
|
INDEX: '/',
|
||||||
CAMERA: '/camera',
|
CAMERA: '/camera',
|
||||||
STREAMING: '/streaming',
|
STREAMING: '/streaming',
|
||||||
|
|||||||
95
src/wasm-lib/Cargo.lock
generated
@ -121,9 +121,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.94"
|
version = "1.0.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
]
|
]
|
||||||
@ -401,9 +401,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.39"
|
version = "0.4.38"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
@ -723,7 +723,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.33"
|
version = "0.1.32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1112,7 +1112,7 @@ dependencies = [
|
|||||||
"fnv",
|
"fnv",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"indexmap 2.7.0",
|
"indexmap 2.7.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -1215,9 +1215,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.2.0"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
|
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@ -1242,7 +1242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1253,7 +1253,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
@ -1303,7 +1303,7 @@ dependencies = [
|
|||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2",
|
"h2",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"httparse",
|
"httparse",
|
||||||
"itoa",
|
"itoa",
|
||||||
@ -1320,7 +1320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"rustls",
|
"rustls",
|
||||||
@ -1340,7 +1340,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@ -1674,17 +1674,16 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.76"
|
version = "0.3.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.29"
|
version = "0.2.28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
@ -1706,7 +1705,7 @@ dependencies = [
|
|||||||
"git_rev",
|
"git_rev",
|
||||||
"gltf-json",
|
"gltf-json",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"iai",
|
"iai",
|
||||||
"image",
|
"image",
|
||||||
"indexmap 2.7.0",
|
"indexmap 2.7.0",
|
||||||
@ -1752,7 +1751,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
version = "0.1.19"
|
version = "0.1.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hyper 0.14.30",
|
"hyper 0.14.30",
|
||||||
@ -1793,7 +1792,7 @@ dependencies = [
|
|||||||
"data-encoding",
|
"data-encoding",
|
||||||
"format_serde_error",
|
"format_serde_error",
|
||||||
"futures",
|
"futures",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
"log",
|
"log",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
@ -1819,9 +1818,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad-modeling-cmds"
|
name = "kittycad-modeling-cmds"
|
||||||
version = "0.2.79"
|
version = "0.2.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10a9cab4476455be70ea57643c31444068b056d091bd348cab6044c0d8ad7fcc"
|
checksum = "3b77259b37acafa360d98af27431ac394bc8899eeed7037513832ddbee856811"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1829,7 +1828,7 @@ dependencies = [
|
|||||||
"enum-iterator",
|
"enum-iterator",
|
||||||
"enum-iterator-derive",
|
"enum-iterator-derive",
|
||||||
"euler",
|
"euler",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"kittycad-modeling-cmds-macros",
|
"kittycad-modeling-cmds-macros",
|
||||||
"kittycad-unit-conversion-derive",
|
"kittycad-unit-conversion-derive",
|
||||||
"measurements",
|
"measurements",
|
||||||
@ -2863,7 +2862,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2",
|
"h2",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
@ -2905,7 +2904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b"
|
checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"reqwest-middleware",
|
"reqwest-middleware",
|
||||||
]
|
]
|
||||||
@ -2918,7 +2917,7 @@ checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 1.0.68",
|
"thiserror 1.0.68",
|
||||||
@ -2935,7 +2934,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"futures",
|
"futures",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -2956,7 +2955,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"matchit",
|
"matchit",
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -3200,9 +3199,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.216"
|
version = "1.0.215"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -3218,9 +3217,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.216"
|
version = "1.0.215"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -4028,7 +4027,7 @@ dependencies = [
|
|||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"http 1.2.0",
|
"http 1.1.0",
|
||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
@ -4236,9 +4235,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.99"
|
version = "0.2.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -4247,12 +4246,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.99"
|
version = "0.2.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.87",
|
"syn 2.0.87",
|
||||||
@ -4261,23 +4261,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.49"
|
version = "0.4.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
|
checksum = "65471f79c1022ffa5291d33520cbbb53b7687b01c2f8e83b57d102eed7ed479d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.99"
|
version = "0.2.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@ -4285,9 +4284,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.99"
|
version = "0.2.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -4298,9 +4297,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.99"
|
version = "0.2.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-lib"
|
name = "wasm-lib"
|
||||||
@ -4362,9 +4361,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.76"
|
version = "0.3.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
|
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|||||||
@ -20,8 +20,8 @@ serde_json = "1.0.128"
|
|||||||
tokio = { version = "1.41.1", features = ["sync"] }
|
tokio = { version = "1.41.1", features = ["sync"] }
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||||
wasm-bindgen = "0.2.99"
|
wasm-bindgen = "0.2.91"
|
||||||
wasm-bindgen-futures = "0.4.49"
|
wasm-bindgen-futures = "0.4.44"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
@ -43,7 +43,7 @@ wasm-bindgen-futures = { version = "0.4.44", features = ["futures-core-03-stream
|
|||||||
wasm-streams = "0.4.1"
|
wasm-streams = "0.4.1"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
||||||
version = "0.3.76"
|
version = "0.3.72"
|
||||||
features = [
|
features = [
|
||||||
"console",
|
"console",
|
||||||
"HtmlTextAreaElement",
|
"HtmlTextAreaElement",
|
||||||
@ -76,7 +76,7 @@ members = [
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
http = "1"
|
http = "1"
|
||||||
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
|
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
|
||||||
kittycad-modeling-cmds = { version = "0.2.79", features = ["websocket"] }
|
kittycad-modeling-cmds = { version = "0.2.77", features = ["websocket"] }
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
assertions_on_result_states = "warn"
|
assertions_on_result_states = "warn"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
description = "A tool for generating documentation from Rust derive macros"
|
description = "A tool for generating documentation from Rust derive macros"
|
||||||
version = "0.1.33"
|
version = "0.1.32"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
|||||||
@ -6,8 +6,6 @@
|
|||||||
mod tests;
|
mod tests;
|
||||||
mod unbox;
|
mod unbox;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use convert_case::Casing;
|
use convert_case::Casing;
|
||||||
use inflector::Inflector;
|
use inflector::Inflector;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
@ -49,10 +47,6 @@ struct StdlibMetadata {
|
|||||||
/// If false, all arguments require labels.
|
/// If false, all arguments require labels.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
unlabeled_first: bool,
|
unlabeled_first: bool,
|
||||||
|
|
||||||
/// Key = argument name, value = argument doc.
|
|
||||||
#[serde(default)]
|
|
||||||
arg_docs: HashMap<String, String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
@ -288,17 +282,6 @@ fn do_stdlib_inner(
|
|||||||
|
|
||||||
let ty_string = rust_type_to_openapi_type(&ty_string);
|
let ty_string = rust_type_to_openapi_type(&ty_string);
|
||||||
let required = !ty_ident.to_string().starts_with("Option <");
|
let required = !ty_ident.to_string().starts_with("Option <");
|
||||||
let description = if let Some(s) = metadata.arg_docs.get(&arg_name) {
|
|
||||||
quote! { #s }
|
|
||||||
} else if metadata.keywords && ty_string != "Args" && ty_string != "ExecState" {
|
|
||||||
errors.push(Error::new_spanned(
|
|
||||||
&arg,
|
|
||||||
"Argument was not documented in the arg_docs block",
|
|
||||||
));
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
quote! { String::new() }
|
|
||||||
};
|
|
||||||
let label_required = !(i == 0 && metadata.unlabeled_first);
|
let label_required = !(i == 0 && metadata.unlabeled_first);
|
||||||
if ty_string != "ExecState" && ty_string != "Args" {
|
if ty_string != "ExecState" && ty_string != "Args" {
|
||||||
let schema = quote! {
|
let schema = quote! {
|
||||||
@ -311,7 +294,6 @@ fn do_stdlib_inner(
|
|||||||
schema: #schema,
|
schema: #schema,
|
||||||
required: #required,
|
required: #required,
|
||||||
label_required: #label_required,
|
label_required: #label_required,
|
||||||
description: #description.to_string(),
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -373,7 +355,6 @@ fn do_stdlib_inner(
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
description: String::new(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -116,9 +116,6 @@ fn test_stdlib_line_to() {
|
|||||||
let (item, errors) = do_stdlib(
|
let (item, errors) = do_stdlib(
|
||||||
quote! {
|
quote! {
|
||||||
name = "lineTo",
|
name = "lineTo",
|
||||||
arg_docs = {
|
|
||||||
sketch = "the sketch you're adding the line to"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
quote! {
|
quote! {
|
||||||
/// This is some function.
|
/// This is some function.
|
||||||
|
|||||||
@ -91,7 +91,6 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
schema: generator.root_schema_for::<Foo>(),
|
schema: generator.root_schema_for::<Foo>(),
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
description: String::new().to_string(),
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +105,6 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
description: String::new(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||