Compare commits
32 Commits
pierremtb/
...
pierremtb/
| Author | SHA1 | Date | |
|---|---|---|---|
| 34272b872d | |||
| 668e2afb99 | |||
| 548c664db0 | |||
| d3a3f4410c | |||
| 22eb343171 | |||
| f2cfa4d5cf | |||
| 3f1f40eeba | |||
| ff2d161606 | |||
| 210c78029d | |||
| e27840219b | |||
| c943a3f192 | |||
| 6aa588f09f | |||
| 59a6333aad | |||
| 403f1507ae | |||
| eac7b83504 | |||
| 667500d1b9 | |||
| b15aac9f48 | |||
| 54153aa646 | |||
| 943cf21d34 | |||
| 5a6728c45a | |||
| ff2103d493 | |||
| 2dfa8f2176 | |||
| 29ed330326 | |||
| ca2cc825a6 | |||
| 83fe1b7ce0 | |||
| 157b76cc78 | |||
| cf957d880e | |||
| dfc3d19677 | |||
| dd370a9365 | |||
| 2274d6459c | |||
| 32ce857119 | |||
| 88b51da417 |
59
.github/ci-cd-scripts/playwright-browser-chrome.sh
vendored
Executable file
@ -0,0 +1,59 @@
|
||||
# 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,17 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# bash strict mode
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
if [[ "$3" == ubuntu-latest* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --shard=$1/$2 || true
|
||||
elif [[ "$3" == windows-latest* ]]; then
|
||||
yarn test:playwright:electron:windows -- --shard=$1/$2 || true
|
||||
elif [[ "$3" == macos-14* ]]; then
|
||||
yarn test:playwright:electron:macos -- --shard=$1/$2 || true
|
||||
if [[ "$1" == ubuntu-latest* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true
|
||||
elif [[ "$1" == windows-latest* ]]; then
|
||||
yarn test:playwright:electron:windows || true
|
||||
elif [[ "$1" == macos-14* ]]; then
|
||||
yarn test:playwright:electron:macos || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
exit 1
|
||||
@ -30,11 +28,11 @@ while [[ $retry -le $max_retrys ]]; do
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
if [[ "$3" == ubuntu-latest* ]]; then
|
||||
if [[ "$1" == ubuntu-latest* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true
|
||||
elif [[ "$3" == windows-latest* ]]; then
|
||||
elif [[ "$1" == windows-latest* ]]; then
|
||||
yarn test:playwright:electron:windows -- --last-failed || true
|
||||
elif [[ "$3" == macos-14* ]]; then
|
||||
elif [[ "$1" == macos-14* ]]; then
|
||||
yarn test:playwright:electron:macos -- --last-failed || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
|
||||
6
.github/workflows/build-apps.yml
vendored
@ -365,7 +365,7 @@ jobs:
|
||||
- name: Set more complete nightly release notes
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
run: |
|
||||
# Note: prefered going this way instead of a full clone in the checkout step,
|
||||
# Note: preferred going this way instead of a full clone in the checkout step,
|
||||
# see https://github.com/actions/checkout/issues/1471
|
||||
git fetch --prune --unshallow --tags
|
||||
export TAG="nightly-${VERSION}"
|
||||
@ -394,6 +394,10 @@ jobs:
|
||||
parent: false
|
||||
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
|
||||
|
||||
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
run: yarn files:invalidate-bucket:nightly
|
||||
|
||||
- name: Tag nightly commit
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
uses: actions/github-script@v7
|
||||
|
||||
156
.github/workflows/e2e-tests.yml
vendored
@ -1,7 +1,7 @@
|
||||
name: E2E Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main, pierremtb/move-tests-to-electron ]
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
@ -33,13 +33,13 @@ jobs:
|
||||
rust:
|
||||
- 'src/wasm-lib/**'
|
||||
|
||||
electron:
|
||||
browser:
|
||||
timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }}
|
||||
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
||||
name: playwright:browser:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large]
|
||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores]
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
runs-on: ${{ matrix.os }}
|
||||
@ -123,13 +123,13 @@ jobs:
|
||||
if: steps.download-wasm.outcome == 'failure'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: build electron
|
||||
- name: build web
|
||||
run: yarn build:local
|
||||
shell: bash
|
||||
run: yarn tron:package
|
||||
- name: Run ubuntu/chrome snapshots
|
||||
shell: bash
|
||||
run: |
|
||||
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: development
|
||||
@ -186,12 +186,12 @@ jobs:
|
||||
with:
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run playwright/electron flow (with retries)
|
||||
- name: Run playwright/chrome flow (with retries)
|
||||
id: retry
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
.github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
|
||||
.github/ci-cd-scripts/playwright-browser-chrome.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
|
||||
env:
|
||||
CI: true
|
||||
FAIL_ON_CONSOLE_ERRORS: true
|
||||
@ -199,6 +199,11 @@ jobs:
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- name: send to axiom
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
@ -216,3 +221,136 @@ jobs:
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
|
||||
electron:
|
||||
name: playwright:electron:${{matrix.os}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large]
|
||||
timeout-minutes: 60
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: check-rust-changes
|
||||
steps:
|
||||
- name: Tune GitHub-hosted runner network
|
||||
uses: smorimoto/tune-github-hosted-runner-network@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- uses: KittyCAD/action-install-cli@main
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: yarn
|
||||
- name: Cache Playwright Browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright/
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
|
||||
- name: Install Playwright Browsers
|
||||
shell: bash
|
||||
run: yarn playwright install chromium --with-deps
|
||||
- name: Download Wasm Cache
|
||||
id: download-wasm
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
continue-on-error: true
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
name: wasm-bundle
|
||||
workflow: build-and-store-wasm.yml
|
||||
branch: main
|
||||
path: src/wasm-lib/pkg
|
||||
- name: copy wasm blob
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||
shell: bash
|
||||
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
continue-on-error: true
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cache Wasm (because rust diff)
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: OR Cache Wasm (because wasm cache failed)
|
||||
if: steps.download-wasm.outcome == 'failure'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: install good sed
|
||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
||||
shell: bash
|
||||
run: |
|
||||
brew install gnu-sed
|
||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Install vector
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
chmod +x /tmp/vector.sh
|
||||
/tmp/vector.sh -y -no-modify-path
|
||||
mkdir -p /tmp/vector
|
||||
cp .github/workflows/vector.toml /tmp/vector.toml
|
||||
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
|
||||
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
|
||||
cat /tmp/vector.toml
|
||||
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
|
||||
- name: Build Wasm (because rust diff)
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: OR Build Wasm (because wasm cache failed)
|
||||
if: steps.download-wasm.outcome == 'failure'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: build electron
|
||||
shell: bash
|
||||
run: yarn tron:package
|
||||
- uses: actions/download-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run electron tests (with retries)
|
||||
id: retry
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
.github/ci-cd-scripts/playwright-electron.sh ${{ matrix.os }}
|
||||
env:
|
||||
CI: true
|
||||
FAIL_ON_CONSOLE_ERRORS: true
|
||||
NODE_ENV: development
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
IS_UBUNTU: ${{ startsWith(matrix.os, 'ubuntu') && 'true' || 'false' }}
|
||||
#DEBUG: 'pw:browser*'
|
||||
- name: send to axiom
|
||||
if: ${{ !cancelled() && (success() || failure()) && !startsWith(matrix.os, 'windows') }}
|
||||
shell: bash
|
||||
run: |
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
6
.github/workflows/publish-apps-release.yml
vendored
@ -126,11 +126,7 @@ jobs:
|
||||
destination: 'dl.kittycad.io/releases/modeling-app'
|
||||
|
||||
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||
run: |
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/last_download.json" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-linux-arm64.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
|
||||
run: yarn files:invalidate-bucket
|
||||
|
||||
- name: Upload release files to Github
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
|
||||
43
INSTALL.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Setting Up Zoo Modeling App
|
||||
|
||||
Compared to other CAD software, getting Zoo Modeling App up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install.
|
||||
|
||||
## Windows
|
||||
|
||||
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
|
||||
|
||||
2. Once downloaded, run the installer `Zoo Modeling App-{version}-{arch}-win.exe` which should take a few seconds.
|
||||
|
||||
3. The installation happens at `C:\Program Files\Zoo Modeling App`. A shortcut in the start menu is also created so you can run the app easily by clicking on it.
|
||||
|
||||
## macOS
|
||||
|
||||
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
|
||||
|
||||
2. Once downloaded, open the disk image `Zoo Modeling App-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
|
||||
|
||||
3. You can then open your `Applications` directory and double-click on `Zoo Modeling App` to open.
|
||||
|
||||
|
||||
## Linux
|
||||
|
||||
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
|
||||
|
||||
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
|
||||
- On Ubuntu, install the FUSE library with these commands in a terminal.
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install libfuse2
|
||||
```
|
||||
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
|
||||
- Once installed, copy the downloaded `Zoo Modeling App-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
|
||||
|
||||
- `appimaged` should automatically find it and make it executable. If not, run:
|
||||
```bash
|
||||
chmod a+x ~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
|
||||
```
|
||||
|
||||
3. You can double-click on the AppImage to run it, or in a terminal with this command:
|
||||
```bash
|
||||
~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
|
||||
```
|
||||
17
README.md
@ -388,6 +388,23 @@ yarn test:unit:local
|
||||
|
||||
#### E2E Tests
|
||||
|
||||
**Playwright Browser**
|
||||
|
||||
These E2E tests run in a browser (without electron).
|
||||
There are tests that are skipped if they are ran in a windows OS or Linux OS. We can use playwright tags to implement test skipping.
|
||||
|
||||
Breaking down the command `yarn test:playwright:browser:chrome:windows`
|
||||
- The application is `playwright`
|
||||
- The runtime is a `browser`
|
||||
- The specific `browser` is `chrome`
|
||||
- The test should run in a `windows` environment. It will skip tests that are broken or flaky in the windows OS.
|
||||
|
||||
```
|
||||
yarn test:playwright:browser:chrome
|
||||
yarn test:playwright:browser:chrome:windows
|
||||
yarn test:playwright:browser:chrome:ubuntu
|
||||
```
|
||||
|
||||
**Playwright Electron**
|
||||
|
||||
These E2E tests run in electron. There are tests that are skipped if they are ran in a windows, linux, or macos environment. We can use playwright tags to implement test skipping.
|
||||
|
||||
@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
|
||||
```js
|
||||
r = 10 // radius
|
||||
// Call `map`, using an anonymous function instead of a named one.
|
||||
circles = map([1..3], (id) {
|
||||
circles = map([1..3], fn(id) {
|
||||
return startSketchOn("XY")
|
||||
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
||||
})
|
||||
|
||||
@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
|
||||
// an anonymous `add` function as its parameter, instead of declaring a
|
||||
// named function outside.
|
||||
arr = [1, 2, 3]
|
||||
sum = reduce(arr, 0, (i, result_so_far) {
|
||||
sum = reduce(arr, 0, fn(i, result_so_far) {
|
||||
return i + result_so_far
|
||||
})
|
||||
|
||||
@ -84,7 +84,7 @@ fn decagon(radius) {
|
||||
// Use a `reduce` to draw the remaining decagon sides.
|
||||
// For each number in the array 1..10, run the given function,
|
||||
// which takes a partially-sketched decagon and adds one more edge to it.
|
||||
fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {
|
||||
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
|
||||
// Draw one edge of the decagon.
|
||||
x = cos(stepAngle * i) * radius
|
||||
y = sin(stepAngle * i) * radius
|
||||
|
||||
@ -100436,7 +100436,7 @@
|
||||
"deprecated": false,
|
||||
"examples": [
|
||||
"r = 10 // radius\nfn drawCircle(id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n}\n\n// Call `drawCircle`, passing in each element of the array.\n// The outputs from each `drawCircle` form a new array,\n// which is the return value from `map`.\ncircles = map([1..3], drawCircle)",
|
||||
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1..3], (id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n})"
|
||||
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1..3], fn(id) {\n return startSketchOn(\"XY\")\n |> circle({ center = [id * 2 * r, 0], radius = r }, %)\n})"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -146129,8 +146129,8 @@
|
||||
"deprecated": false,
|
||||
"examples": [
|
||||
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n let sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
||||
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, (i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
||||
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * tau()\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchAt([cos(0) * radius, sin(0) * radius])\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return lineTo([x, y], partialDecagon)\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n let stepAngle = (1/10) * tau()\n let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])\n\n // Here's the reduce part.\n let partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n let x = cos(stepAngle * i) * radius\n let y = sin(stepAngle * i) * radius\n partialDecagon = lineTo([x, y], partialDecagon)\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close(%)"
|
||||
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
||||
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * tau()\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchAt([cos(0) * radius, sin(0) * radius])\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return lineTo([x, y], partialDecagon)\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n let stepAngle = (1/10) * tau()\n let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])\n\n // Here's the reduce part.\n let partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n let x = cos(stepAngle * i) * radius\n let y = sin(stepAngle * i) * radius\n partialDecagon = lineTo([x, y], partialDecagon)\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close(%)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,11 +1,22 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { setupElectron, tearDown } from './test-utils'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Electron app header tests', () => {
|
||||
test(
|
||||
'Open Command Palette button has correct shortcut',
|
||||
{ tag: '@electron' },
|
||||
async ({ page }, testInfo) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// No space before the shortcut since it checks textContent.
|
||||
let text
|
||||
@ -23,14 +34,21 @@ test.describe('Electron app header tests', () => {
|
||||
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandsButton).toBeVisible()
|
||||
await expect(commandsButton).toHaveText(text)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'User settings has correct shortcut',
|
||||
{ tag: '@electron' },
|
||||
async ({ page }, testInfo) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Open the user sidebar menu.
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
@ -41,6 +59,8 @@ test.describe('Electron app header tests', () => {
|
||||
const userSettingsButton = page.getByTestId('user-settings')
|
||||
await expect(userSettingsButton).toBeVisible()
|
||||
await expect(userSettingsButton).toHaveText(text)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,26 +1,29 @@
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import {
|
||||
getUtils,
|
||||
TEST_COLORS,
|
||||
setup,
|
||||
tearDown,
|
||||
commonPoints,
|
||||
PERSIST_MODELING_CONTEXT,
|
||||
} 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)
|
||||
|
||||
async function doBasicSketch(
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
openPanes: string[]
|
||||
) {
|
||||
async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.waitForTimeout()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
// If we have the code pane open, we should see the code.
|
||||
@ -145,11 +148,13 @@ async function doBasicSketch(
|
||||
}
|
||||
|
||||
test.describe('Basic sketch', () => {
|
||||
test.fixme('code pane open at start', async ({ page, homePage }) => {
|
||||
await doBasicSketch(page, homePage, ['code'])
|
||||
test('code pane open at start', { tag: ['@skipWin'] }, async ({ page }) => {
|
||||
// Skip on windows it is being weird.
|
||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||
await doBasicSketch(page, ['code'])
|
||||
})
|
||||
|
||||
test.fixme('code pane closed at start', async ({ page, homePage }) => {
|
||||
test('code pane closed at start', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async (persistModelingContext) => {
|
||||
localStorage.setItem(
|
||||
@ -157,6 +162,6 @@ test.describe('Basic sketch', () => {
|
||||
JSON.stringify({ openPanes: [] })
|
||||
)
|
||||
}, PERSIST_MODELING_CONTEXT)
|
||||
await doBasicSketch(page, homePage, [])
|
||||
await doBasicSketch(page, [])
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,21 +1,27 @@
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||
import { getUtils } from './test-utils'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
const sketchOnPlaneAndBackSideTest = async (
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
page: any,
|
||||
plane: string,
|
||||
clickCoords: { x: number; y: number }
|
||||
) => {
|
||||
const u = await getUtils(page)
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const coord =
|
||||
@ -77,39 +83,32 @@ test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
await u.clearCommandLogs()
|
||||
await u.removeCurrentCode()
|
||||
}
|
||||
test('XY', async ({ page, homePage }) => {
|
||||
test('XY', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(
|
||||
page,
|
||||
homePage,
|
||||
'XY',
|
||||
{ x: 600, y: 388 } // red plane
|
||||
// { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too.
|
||||
)
|
||||
})
|
||||
|
||||
test('YZ', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, 'YZ', { x: 700, y: 250 }) // green plane
|
||||
test('YZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, 'YZ', { x: 700, y: 250 }) // green plane
|
||||
})
|
||||
|
||||
test('XZ', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, '-XZ', { x: 700, y: 80 }) // blue plane
|
||||
test('XZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 80 }) // blue plane
|
||||
})
|
||||
|
||||
test('-XY', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, '-XY', {
|
||||
x: 600,
|
||||
y: 118,
|
||||
}) // back of red plane
|
||||
test('-XY', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, '-XY', { x: 600, y: 118 }) // back of red plane
|
||||
})
|
||||
|
||||
test('-YZ', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, '-YZ', {
|
||||
x: 700,
|
||||
y: 219,
|
||||
}) // back of green plan
|
||||
test('-YZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, '-YZ', { x: 700, y: 219 }) // back of green plane
|
||||
})
|
||||
|
||||
test('-XZ', async ({ page, homePage }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, homePage, 'XZ', { x: 700, y: 427 }) // back of blue plane
|
||||
test('-XZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 427 }) // back of blue plane
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,15 +1,28 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, executorInputPath } from './test-utils'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import { join } from 'path'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Code pane and errors', () => {
|
||||
test('Typing KCL errors induces a badge on the code pane button', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -18,18 +31,18 @@ test.describe('Code pane and errors', () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`// Extruded Triangle
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([-5, 10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)`
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([-5, 10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -51,9 +64,9 @@ test.describe('Code pane and errors', () => {
|
||||
|
||||
test('Opening and closing the code pane will consistently show error diagnostics', async ({
|
||||
page,
|
||||
homePage,
|
||||
editor,
|
||||
}) => {
|
||||
await page.goto('http://localhost:3000')
|
||||
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
@ -61,8 +74,8 @@ test.describe('Code pane and errors', () => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 900 })
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 900 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -78,9 +91,8 @@ test.describe('Code pane and errors', () => {
|
||||
await expect(codePaneButtonHolder).not.toContainText('notification')
|
||||
|
||||
// Delete a character to break the KCL
|
||||
await editor.openPane()
|
||||
await editor.scrollToText('thickness, bracketLeg1Sketch)')
|
||||
await page.getByText('extrude(thickness, bracketLeg1Sketch)').click()
|
||||
await u.openKclCodePanel()
|
||||
await page.getByText('thickness, bracketLeg1Sketch)').click()
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
// Ensure that a badge appears on the button
|
||||
@ -104,10 +116,7 @@ test.describe('Code pane and errors', () => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Open the code pane
|
||||
await editor.openPane()
|
||||
|
||||
// Go to our problematic code again (missing closing paren!)
|
||||
await editor.scrollToText('extrude(thickness, bracketLeg1Sketch')
|
||||
await u.openKclCodePanel()
|
||||
|
||||
// Ensure that a badge appears on the button
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
@ -120,18 +129,18 @@ test.describe('Code pane and errors', () => {
|
||||
await expect(page.locator('.cm-tooltip').first()).toBeVisible()
|
||||
})
|
||||
|
||||
test.fixme('When error is not in view you can click the badge to scroll to it', async ({
|
||||
test('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) => {
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
@ -155,24 +164,24 @@ test.describe('Code pane and errors', () => {
|
||||
await expect(
|
||||
page
|
||||
.getByText(
|
||||
'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
|
||||
'sketch profile must lie entirely on one side of the revolution axis'
|
||||
)
|
||||
.first()
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
await context.addInitScript((code) => {
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
@ -232,29 +241,32 @@ test.describe('Code pane and errors', () => {
|
||||
test(
|
||||
'Opening multiple panes persists when switching projects',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
async ({ browserName }, testInfo) => {
|
||||
// Setup multiple projects.
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([
|
||||
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||
fsp.mkdir(bracketDir, { recursive: true }),
|
||||
])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([
|
||||
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||
fsp.mkdir(bracketDir, { recursive: true }),
|
||||
])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await test.step('Opening the bracket project should load', async () => {
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
@ -297,21 +309,30 @@ test(
|
||||
await expect(page.locator('#variables-pane')).toBeVisible()
|
||||
await expect(page.locator('#logs-pane')).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'external change of file contents are reflected in editor',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
async ({ browserName }, testInfo) => {
|
||||
const PROJECT_DIR_NAME = 'lee-was-here'
|
||||
const { dir: projectsDir } = await context.folderSetupFn(async (dir) => {
|
||||
const aProjectDir = join(dir, PROJECT_DIR_NAME)
|
||||
await fsp.mkdir(aProjectDir, { recursive: true })
|
||||
const {
|
||||
electronApp,
|
||||
page,
|
||||
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)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await test.step('Open the project', async () => {
|
||||
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
|
||||
@ -330,5 +351,7 @@ test(
|
||||
)
|
||||
await u.editorTextMatches(content)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
@ -1,30 +1,37 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils } from './test-utils'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Command bar tests', () => {
|
||||
test('Extrude from command bar selects extrude line after', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
|> close(%)
|
||||
`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
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 u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -45,24 +52,24 @@ test.describe('Command bar tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('Fillet from command bar', async ({ page, homePage }) => {
|
||||
test('Fillet from command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(-10, sketch001)`
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(-10, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
@ -86,10 +93,10 @@ test.describe('Command bar tests', () => {
|
||||
|
||||
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
@ -146,7 +153,7 @@ test.describe('Command bar tests', () => {
|
||||
// Check that the visibility changed
|
||||
await expect(paneSelector).not.toBeVisible()
|
||||
|
||||
commandOptionInput = page.locator('[id="option-input"]')
|
||||
commandOptionInput = page.getByPlaceholder('off')
|
||||
|
||||
// Test case for https://github.com/KittyCAD/modeling-app/issues/2882
|
||||
await commandBarButton.click()
|
||||
@ -167,10 +174,10 @@ test.describe('Command bar tests', () => {
|
||||
|
||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
@ -214,25 +221,25 @@ test.describe('Command bar tests', () => {
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
})
|
||||
|
||||
test('Can extrude from the command bar', async ({ page, homePage }) => {
|
||||
test('Can extrude from the command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`distance = sqrt(20)
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-6.95, 10.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -20.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
`
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-6.95, 10.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -20.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
// Make sure the stream is up
|
||||
await u.openDebugPanel()
|
||||
@ -286,19 +293,26 @@ test.describe('Command bar tests', () => {
|
||||
await continueButton.click()
|
||||
await submitButton.click()
|
||||
|
||||
// Check that the code was updated
|
||||
await u.waitForCmdReceive('extrude')
|
||||
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
'extrude001 = extrude(distance001, sketch001)'
|
||||
// Unfortunately this indentation seems to matter for the test
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`distance = sqrt(20)
|
||||
distance001 = ${KCL_DEFAULT_LENGTH}
|
||||
sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-6.95, 10.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -20.93], %)
|
||||
|> line([-23.44, 0.52], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(distance001, sketch001)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
|
||||
)
|
||||
})
|
||||
|
||||
test('Can switch between sketch tools via command bar', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
test('Can switch between sketch tools via command bar', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
||||
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
|
||||
@ -1,16 +1,23 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { getUtils } from './test-utils'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
test.describe('Copilot ghost text', () => {
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
test.skip(true, 'Needs to get covered again')
|
||||
|
||||
test('completes code in empty file', async ({ page, homePage }) => {
|
||||
test('completes code in empty file', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -45,13 +52,12 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
test.skip('copilot disabled in sketch mode no select plane', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -95,13 +101,12 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
test('copilot disabled in sketch mode after selecting plane', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -179,12 +184,12 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('ArrowUp in code rejects the suggestion', async ({ page, homePage }) => {
|
||||
test('ArrowUp in code rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -207,15 +212,12 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('ArrowDown in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('ArrowDown in code rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -238,15 +240,12 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('ArrowLeft in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('ArrowLeft in code rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -269,15 +268,12 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('ArrowRight in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('ArrowRight in code rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -300,12 +296,12 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('Enter in code scoots it down', async ({ page, homePage }) => {
|
||||
test('Enter in code scoots it down', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -330,15 +326,12 @@ test.describe('Copilot ghost text', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('Ctrl+shift+z in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('Ctrl+shift+z in code rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -367,13 +360,12 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
test('Ctrl+z in code rejects the suggestion and undos the last code', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForTimeout(800)
|
||||
await u.codeLocator.click()
|
||||
@ -428,107 +420,98 @@ test.describe('Copilot ghost text', () => {
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
// TODO when we make codemirror a widget, we can test this.
|
||||
//await expect(page.locator('.cm-content')).toHaveText(``) })
|
||||
//await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('delete in code rejects the suggestion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
test('delete in code rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
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-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, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
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-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, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.press('Delete')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.press('Delete')
|
||||
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,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
test('backspace in code rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
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-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, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
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-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, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.press('Backspace')
|
||||
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,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
test('focus outside code pane rejects the suggestion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
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, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
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, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// Going outside the editor should hide the ghost text.
|
||||
await page.mouse.move(0, 0)
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
// Going outside the editor should hide the ghost text.
|
||||
await page.mouse.move(0, 0)
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils } from './test-utils'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
function countNewlines(input: string): number {
|
||||
let count = 0
|
||||
@ -16,14 +24,13 @@ test.describe('Debug pane', () => {
|
||||
test('Artifact IDs in the artifact graph are stable across code edits', async ({
|
||||
page,
|
||||
context,
|
||||
homePage,
|
||||
}) => {
|
||||
const code = `sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([1, 1], %)
|
||||
`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([1, 1], %)
|
||||
`
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
const tree = page.getByTestId('debug-feature-tree')
|
||||
const segment = tree.locator('li', {
|
||||
@ -32,7 +39,7 @@ test.describe('Debug pane', () => {
|
||||
})
|
||||
|
||||
await test.step('Test setup', async () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openKclCodePanel()
|
||||
await u.openDebugPanel()
|
||||
// Set the code in the code editor.
|
||||
|
||||
@ -1,31 +1,39 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import path from 'path'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { join } from 'path'
|
||||
import {
|
||||
getUtils,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
getPlaywrightDownloadDir,
|
||||
} from './test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test(
|
||||
'export works on the first try',
|
||||
{ tag: '@electron' },
|
||||
async ({ page, context }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
path.join(bracketDir, 'other.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(bracketDir, 'other.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
@ -85,16 +93,12 @@ test(
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
|
||||
const firstFileFullPath = path.resolve(
|
||||
getPlaywrightDownloadDir(page),
|
||||
exportFileName
|
||||
)
|
||||
await test.step('Check the export size', async () => {
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile(firstFileFullPath)
|
||||
const outputGltf = await fsp.readFile(exportFileName)
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
return 0
|
||||
@ -103,6 +107,9 @@ test(
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBeGreaterThan(300_000)
|
||||
|
||||
// clean up exported file
|
||||
await fsp.rm(exportFileName)
|
||||
})
|
||||
})
|
||||
|
||||
@ -163,16 +170,12 @@ test(
|
||||
expect(exportingToastMessage).not.toBeVisible(),
|
||||
]))
|
||||
|
||||
const secondFileFullPath = path.resolve(
|
||||
getPlaywrightDownloadDir(page),
|
||||
exportFileName
|
||||
)
|
||||
await test.step('Check the export size', async () => {
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile(secondFileFullPath)
|
||||
const outputGltf = await fsp.readFile(exportFileName)
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
return 0
|
||||
@ -181,7 +184,13 @@ test(
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBeGreaterThan(100_000)
|
||||
|
||||
// clean up exported file
|
||||
await fsp.rm(exportFileName)
|
||||
})
|
||||
await electronApp.close()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import fsp from 'fs/promises'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import {
|
||||
@ -6,27 +6,37 @@ import {
|
||||
darkModePlaneColorXZ,
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
|
||||
import { join } from 'path'
|
||||
|
||||
test.describe('Editor tests', () => {
|
||||
test('can comment out code with ctrl+/', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Editor tests', () => {
|
||||
test('can comment out code with ctrl+/', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('/')
|
||||
@ -34,11 +44,11 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
// |> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
// |> close(%)`)
|
||||
|
||||
// uncomment the code
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
@ -47,63 +57,61 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('if you click the format button it formats your code', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> 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], %)
|
||||
|> 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], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('if you click the format button it formats your code and executes so lints are still there', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -127,11 +135,11 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
@ -143,27 +151,29 @@ test.describe('Editor tests', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('fold gutters work', async ({ page, homePage }) => {
|
||||
test('fold gutters work', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
const fullCode = `sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// TODO: Jess needs to fix this but you have to mod the code to get them to show
|
||||
// up, its an annoying codemirror thing.
|
||||
@ -214,25 +224,22 @@ test.describe('Editor tests', () => {
|
||||
await expect(foldGutterFoldLine).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('hover over functions shows function description', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('hover over functions shows function description', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
@ -261,24 +268,23 @@ test.describe('Editor tests', () => {
|
||||
|
||||
test('if you use the format keyboard binding it formats your code', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
@ -295,33 +301,32 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('if you use the format keyboard binding it formats your code and executes so lints are shown', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -348,11 +353,11 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
@ -364,14 +369,11 @@ test.describe('Editor tests', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('if you write kcl with lint errors you get lints', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('if you write kcl with lint errors you get lints', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
||||
@ -407,26 +409,23 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('if you fixup kcl errors you clear lints', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('if you fixup kcl errors you clear lints', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> close(%)
|
||||
`
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
@ -448,23 +447,20 @@ test.describe('Editor tests', () => {
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('if you write invalid kcl you get inlined errors', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
/* add the following code to the editor ($ error is not a valid line)
|
||||
$ error
|
||||
const topAng = 30
|
||||
const bottomAng = 25
|
||||
*/
|
||||
$ error
|
||||
topAng = 30
|
||||
bottomAng = 25
|
||||
*/
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('$ error')
|
||||
|
||||
@ -478,12 +474,14 @@ test.describe('Editor tests', () => {
|
||||
await page.keyboard.type('bottomAng = 25')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// error in guter
|
||||
// error in gutter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
|
||||
await expect(
|
||||
page.getByText('Tag names must not be empty').first()
|
||||
).toBeVisible()
|
||||
|
||||
// select the line that's causing the error and delete it
|
||||
await page.getByText('$ error').click()
|
||||
@ -522,36 +520,34 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.fixme('error with 2 source ranges gets 2 diagnostics', async ({
|
||||
// TODO currently multiple source ranges are not supported
|
||||
test.skip('error with 2 source ranges gets 2 diagnostics', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`length = .750
|
||||
width = 0.500
|
||||
height = 0.500
|
||||
dia = 4
|
||||
|
||||
fn squareHole = (l, w) => {
|
||||
squareHoleSketch = startSketchOn('XY')
|
||||
|> startProfileAt([-width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, length / 2], %)
|
||||
|> lineTo([-width / 2, length / 2], %)
|
||||
|> close(%)
|
||||
return squareHoleSketch
|
||||
}
|
||||
`
|
||||
width = 0.500
|
||||
height = 0.500
|
||||
dia = 4
|
||||
|
||||
fn squareHole = (l, w) => {
|
||||
squareHoleSketch = startSketchOn('XY')
|
||||
|> startProfileAt([-width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, -length / 2], %)
|
||||
|> lineTo([width / 2, length / 2], %)
|
||||
|> lineTo([-width / 2, length / 2], %)
|
||||
|> close(%)
|
||||
return squareHoleSketch
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.waitForTimeout(1000)
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -577,9 +573,9 @@ test.describe('Editor tests', () => {
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
||||
|> circle({ center: [0, 0], radius: dia/2 }, %)
|
||||
|> hole(squareHole(length, width, height), %)
|
||||
|> extrude(height, %)`)
|
||||
|> circle({ center = [0, 0], radius = dia/2 }, %)
|
||||
|> hole(squareHole(length, width, height), %)
|
||||
|> extrude(height, %)`)
|
||||
|
||||
// error in gutter
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
@ -592,38 +588,38 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
||||
})
|
||||
test('if your kcl gets an error from the engine it is inlined', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await context.addInitScript(async () => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`box = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %, $revolveAxis)
|
||||
|> close(%)
|
||||
|> extrude(10, %)
|
||||
|
||||
sketch001 = startSketchOn(box, revolveAxis)
|
||||
|> startProfileAt([5, 10], %)
|
||||
|> line([0, -10], %)
|
||||
|> line([2, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> close(%)
|
||||
|> revolve({
|
||||
axis: revolveAxis,
|
||||
angle: 90
|
||||
}, %)
|
||||
`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %, $revolveAxis)
|
||||
|> close(%)
|
||||
|> extrude(10, %)
|
||||
|
||||
sketch001 = startSketchOn(box, revolveAxis)
|
||||
|> startProfileAt([5, 10], %)
|
||||
|> line([0, -10], %)
|
||||
|> line([2, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> close(%)
|
||||
|> revolve({
|
||||
axis = revolveAxis,
|
||||
angle = 90
|
||||
}, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await page.goto('/')
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
@ -634,15 +630,12 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.getByText(searchText)).toBeVisible()
|
||||
})
|
||||
test.describe('Autocomplete works', () => {
|
||||
test('with enter/click to accept the completion', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('with enter/click to accept the completion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// tests clicking on an option, selection the first option
|
||||
// and arrowing down to an option
|
||||
@ -704,19 +697,19 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
|
||||
// expect there to be no KCL errors
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('with tab to accept the completion', async ({ page, homePage }) => {
|
||||
test('with tab to accept the completion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// this test might be brittle as we add and remove functions
|
||||
// but should also be easy to update.
|
||||
@ -778,30 +771,26 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
})
|
||||
})
|
||||
test('Can undo a click and point extrude with ctrl+z', async ({
|
||||
page,
|
||||
context,
|
||||
homePage,
|
||||
}) => {
|
||||
test('Can undo a click and point extrude with ctrl+z', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -854,32 +843,29 @@ test.describe('Editor tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`)
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('Can undo a sketch modification with ctrl+z', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('Can undo a sketch modification with ctrl+z', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -906,7 +892,7 @@ test.describe('Editor tests', () => {
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [1200 / 2, 500 / 2]
|
||||
const startPX = [665, 397]
|
||||
|
||||
const dragPX = 40
|
||||
|
||||
@ -920,9 +906,9 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfileAt handle
|
||||
// drag startProfieAt handle
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 },
|
||||
sourcePosition: { x: startPX[0], y: startPX[1] },
|
||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
@ -960,12 +946,12 @@ test.describe('Editor tests', () => {
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line([15.4, -2.78], %)
|
||||
|> tangentialArcTo([27.6, -3.05], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([15.39, -2.78], %)
|
||||
|> tangentialArcTo([27.6, -3.05], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|
||||
// Hit undo
|
||||
await page.keyboard.down('Control')
|
||||
@ -974,11 +960,11 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line([15.4, -2.78], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([15.39, -2.78], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
@ -987,12 +973,12 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
@ -1002,29 +988,31 @@ test.describe('Editor tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
})
|
||||
|
||||
test.fixme(
|
||||
`Can use the import stdlib function on a local OBJ file`,
|
||||
{ tag: '@electron' },
|
||||
async ({ page, context }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'cube')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.obj'),
|
||||
join(bracketDir, 'cube.obj')
|
||||
)
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'cube')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.obj'),
|
||||
join(bracketDir, 'cube.obj')
|
||||
)
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
|
||||
},
|
||||
})
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setBodyDimensions(viewportSize)
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
// Locators and constants
|
||||
const u = await getUtils(page)
|
||||
@ -1082,6 +1070,8 @@ test.describe('Editor tests', () => {
|
||||
})
|
||||
.toBeGreaterThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -54,13 +54,13 @@ export class EditorFixture {
|
||||
}
|
||||
}
|
||||
if (!shouldNormalise) {
|
||||
const expectStart = expect.poll(() => this.codeContent.textContent())
|
||||
const expectStart = expect(this.codeContent)
|
||||
if (not) {
|
||||
const result = await expectStart.not.toContain(code)
|
||||
const result = await expectStart.not.toContainText(code, { timeout })
|
||||
await resetPane()
|
||||
return result
|
||||
}
|
||||
const result = await expectStart.toContain(code)
|
||||
const result = await expectStart.toContainText(code, { timeout })
|
||||
await resetPane()
|
||||
return result
|
||||
}
|
||||
@ -147,20 +147,4 @@ export class EditorFixture {
|
||||
openPane() {
|
||||
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 {
|
||||
BrowserContext,
|
||||
ElectronApplication,
|
||||
TestInfo,
|
||||
Page,
|
||||
TestInfo,
|
||||
} from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, setupElectron } from '../test-utils'
|
||||
import { test as base } from '@playwright/test'
|
||||
import { getUtils, setup, setupElectron, tearDown } from '../test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { CmdBarFixture } from './cmdBarFixture'
|
||||
@ -20,11 +20,11 @@ export class AuthenticatedApp {
|
||||
public readonly page: Page
|
||||
public readonly context: BrowserContext
|
||||
public readonly testInfo: TestInfo
|
||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
||||
public readonly viewPortSize = { width: 1000, height: 500 }
|
||||
|
||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||
this.context = context
|
||||
this.page = page
|
||||
this.context = context
|
||||
this.testInfo = testInfo
|
||||
}
|
||||
|
||||
@ -49,7 +49,9 @@ export class AuthenticatedApp {
|
||||
}
|
||||
}
|
||||
|
||||
export interface Fixtures {
|
||||
interface Fixtures {
|
||||
app: AuthenticatedApp
|
||||
tronApp: AuthenticatedTronApp
|
||||
cmdBar: CmdBarFixture
|
||||
editor: EditorFixture
|
||||
toolbar: ToolbarFixture
|
||||
@ -59,11 +61,9 @@ export interface Fixtures {
|
||||
export class AuthenticatedTronApp {
|
||||
public readonly _page: Page
|
||||
public page: Page
|
||||
public context: BrowserContext
|
||||
public readonly context: BrowserContext
|
||||
public readonly testInfo: TestInfo
|
||||
public electronApp?: ElectronApplication
|
||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
||||
public dir: string = ''
|
||||
|
||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||
this._page = page
|
||||
@ -79,24 +79,15 @@ export class AuthenticatedTronApp {
|
||||
appSettings?: Partial<SaveSettingsPayload>
|
||||
} = { fixtures: {} }
|
||||
) {
|
||||
const { electronApp, page, context, dir } = await setupElectron({
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo: this.testInfo,
|
||||
folderSetupFn: arg.folderSetupFn,
|
||||
cleanProjectDir: arg.cleanProjectDir,
|
||||
appSettings: arg.appSettings,
|
||||
})
|
||||
this.page = page
|
||||
this.context = context
|
||||
this.electronApp = electronApp
|
||||
this.dir = dir
|
||||
|
||||
// Easier to access throughout utils
|
||||
this.page.dir = dir
|
||||
|
||||
// Setup localStorage, addCookies, reload
|
||||
await setup(this.context, this.page, this.testInfo)
|
||||
|
||||
await page.setViewportSize(this.viewPortSize)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
for (const key of unsafeTypedKeys(arg.fixtures)) {
|
||||
const fixture = arg.fixtures[key]
|
||||
@ -119,20 +110,32 @@ export class AuthenticatedTronApp {
|
||||
})
|
||||
}
|
||||
|
||||
export const fixtures = {
|
||||
cmdBar: async ({ page }: { page: Page }, use: any) => {
|
||||
export const test = base.extend<Fixtures>({
|
||||
app: async ({ page, context }, use, testInfo) => {
|
||||
await use(new AuthenticatedApp(context, page, testInfo))
|
||||
},
|
||||
tronApp: async ({ page, context }, use, testInfo) => {
|
||||
await use(new AuthenticatedTronApp(context, page, testInfo))
|
||||
},
|
||||
cmdBar: async ({ page }, use) => {
|
||||
await use(new CmdBarFixture(page))
|
||||
},
|
||||
editor: async ({ page }: { page: Page }, use: any) => {
|
||||
editor: async ({ page }, use) => {
|
||||
await use(new EditorFixture(page))
|
||||
},
|
||||
toolbar: async ({ page }: { page: Page }, use: any) => {
|
||||
toolbar: async ({ page }, use) => {
|
||||
await use(new ToolbarFixture(page))
|
||||
},
|
||||
scene: async ({ page }: { page: Page }, use: any) => {
|
||||
scene: async ({ page }, use) => {
|
||||
await use(new SceneFixture(page))
|
||||
},
|
||||
homePage: async ({ page }: { page: Page }, use: any) => {
|
||||
homePage: async ({ page }, use) => {
|
||||
await use(new HomePageFixture(page))
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
export { expect } from '@playwright/test'
|
||||
|
||||
@ -14,14 +14,10 @@ interface HomePageState {
|
||||
export class HomePageFixture {
|
||||
public page: Page
|
||||
|
||||
projectSection!: Locator
|
||||
projectCard!: Locator
|
||||
projectCardTitle!: Locator
|
||||
projectCardFile!: Locator
|
||||
projectCardFolder!: Locator
|
||||
projectButtonNew!: Locator
|
||||
projectButtonContinue!: Locator
|
||||
projectTextName!: Locator
|
||||
sortByDateBtn!: Locator
|
||||
sortByNameBtn!: Locator
|
||||
|
||||
@ -32,19 +28,11 @@ export class HomePageFixture {
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
|
||||
this.projectSection = this.page.getByTestId('home-section')
|
||||
|
||||
this.projectCard = this.page.getByTestId('project-link')
|
||||
this.projectCardTitle = this.page.getByTestId('project-title')
|
||||
this.projectCardFile = this.page.getByTestId('project-file-count')
|
||||
this.projectCardFolder = this.page.getByTestId('project-folder-count')
|
||||
|
||||
this.projectButtonNew = this.page.getByTestId('home-new-file')
|
||||
this.projectTextName = this.page.getByTestId('cmd-bar-arg-value')
|
||||
this.projectButtonContinue = this.page.getByRole('button', {
|
||||
name: 'Continue',
|
||||
})
|
||||
|
||||
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||
}
|
||||
@ -103,25 +91,10 @@ export class HomePageFixture {
|
||||
.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) => {
|
||||
const projectCard = this.projectCard.locator(
|
||||
this.page.getByText(projectTitle)
|
||||
)
|
||||
await projectCard.click()
|
||||
}
|
||||
|
||||
goToModelingScene = async (name: string = 'testDefault') => {
|
||||
// On web this is a no-op. There is no project view.
|
||||
if (process.env.PLATFORM === 'web') return
|
||||
|
||||
await this.createAndGoToProject(name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,9 +53,8 @@ export class SceneFixture {
|
||||
|
||||
expectState = async (expected: SceneSerialised) => {
|
||||
return expect
|
||||
.poll(async () => await this._serialiseScene(), {
|
||||
intervals: [1_000, 2_000, 10_000],
|
||||
timeout: 60000,
|
||||
.poll(() => this._serialiseScene(), {
|
||||
message: `Expected scene state to match`,
|
||||
})
|
||||
.toEqual(expected)
|
||||
}
|
||||
@ -188,10 +187,7 @@ export class SceneFixture {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await this.page
|
||||
.locator(`[data-receive-command-type="default_camera_get_settings"]`)
|
||||
.first()
|
||||
.waitFor()
|
||||
await this.waitForExecutionDone()
|
||||
const position = await Promise.all([
|
||||
this.page.getByTestId('cam-x-position').inputValue().then(Number),
|
||||
this.page.getByTestId('cam-y-position').inputValue().then(Number),
|
||||
@ -242,7 +238,6 @@ export class SceneFixture {
|
||||
}
|
||||
|
||||
async clickGizmoMenuItem(name: string) {
|
||||
await this.gizmo.hover()
|
||||
await this.gizmo.click({ button: 'right' })
|
||||
const buttonToTest = this.page.getByRole('button', {
|
||||
name: name,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { expect } from '../zoo-test'
|
||||
import { expect } from './fixtureSetup'
|
||||
import { doAndWaitForImageDiff } from '../test-utils'
|
||||
|
||||
export class ToolbarFixture {
|
||||
@ -7,6 +7,7 @@ export class ToolbarFixture {
|
||||
|
||||
extrudeButton!: Locator
|
||||
loftButton!: Locator
|
||||
shellButton!: Locator
|
||||
offsetPlaneButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
lineBtn!: Locator
|
||||
@ -28,6 +29,7 @@ export class ToolbarFixture {
|
||||
this.page = page
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.loftButton = page.getByTestId('loft')
|
||||
this.shellButton = page.getByTestId('shell')
|
||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.lineBtn = page.getByTestId('line')
|
||||
|
||||
@ -1,22 +1,29 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { executorInputPath } from './test-utils'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { setupElectron, tearDown, executorInputPath } from './test-utils'
|
||||
import { join } from 'path'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test(
|
||||
'When machine-api server not found butt is disabled and shows the reason',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
@ -40,23 +47,28 @@ test(
|
||||
// that the machine-api server is not found
|
||||
await makeButton.hover()
|
||||
await expect(page.getByText(notFoundText).first()).toBeVisible()
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'When machine-api server not found home screen & project status shows the reason',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
const notFoundText = 'Machine API server was not discovered'
|
||||
|
||||
@ -79,5 +91,7 @@ test(
|
||||
|
||||
await networkMachineToggle.hover()
|
||||
await expect(page.getByText(notFoundText).nth(1)).toBeVisible()
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
// 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,63 +1,79 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { join } from 'path'
|
||||
import fsp from 'fs/promises'
|
||||
import { getUtils, executorInputPath, createProject } from './test-utils'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
createProject,
|
||||
} from './test-utils'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import {
|
||||
TEST_SETTINGS_KEY,
|
||||
TEST_SETTINGS_ONBOARDING_START,
|
||||
TEST_SETTINGS_ONBOARDING_EXPORT,
|
||||
TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING,
|
||||
TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||
} from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
|
||||
// Because onboarding relies on an app setting we need to set it as incompletel
|
||||
// for all these tests.
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
if (testInfo.tags.includes('@electron')) {
|
||||
return
|
||||
}
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Onboarding tests', () => {
|
||||
test(
|
||||
'Onboarding code is shown in the editor',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
test('Onboarding code is shown in the editor', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey }) => {
|
||||
// Give no initial code, so that the onboarding start is shown immediately
|
||||
localStorage.removeItem('persistCode')
|
||||
localStorage.removeItem(settingsKey)
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
{ settingsKey: TEST_SETTINGS_KEY }
|
||||
)
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
await expect(
|
||||
page.getByText('Welcome to Modeling App! This')
|
||||
).toBeVisible()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// *and* that the code is shown in the editor
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
'// Shelf Bracket'
|
||||
)
|
||||
}
|
||||
)
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||
|
||||
// *and* that the code is shown in the editor
|
||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||
})
|
||||
|
||||
test(
|
||||
'Desktop: fresh onboarding executes and loads',
|
||||
{
|
||||
tag: '@electron',
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ page, homePage }, testInfo) => {
|
||||
cleanProjectDir: true,
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setBodyDimensions(viewportSize)
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
await test.step(`Create a project and open to the onboarding`, async () => {
|
||||
await createProject({ name: 'project-link', page })
|
||||
@ -77,361 +93,320 @@ test.describe('Onboarding tests', () => {
|
||||
'// Shelf Bracket'
|
||||
)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Code resets after confirmation',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')`
|
||||
test('Code resets after confirmation', async ({ page }) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')`
|
||||
|
||||
// Load the page up with some code so we see the confirmation warning
|
||||
// when we go to replay onboarding
|
||||
await context.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, initialCode)
|
||||
// Load the page up with some code so we see the confirmation warning
|
||||
// when we go to replay onboarding
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, initialCode)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Replay the onboarding
|
||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||
const replayButton = page.getByRole('button', {
|
||||
name: 'Replay onboarding',
|
||||
// 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')
|
||||
})
|
||||
await expect(replayButton).toBeVisible()
|
||||
await replayButton.click()
|
||||
).toContain('// Shelf Bracket')
|
||||
})
|
||||
|
||||
// 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)
|
||||
test('Click through each onboarding step', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
await nextButton.hover()
|
||||
await nextButton.click()
|
||||
|
||||
// Ensure we see the introduction and that the code has been reset
|
||||
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
'// Shelf Bracket'
|
||||
)
|
||||
|
||||
// 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.
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Click through each onboarding step',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
// 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)
|
||||
},
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
// 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()
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }),
|
||||
}
|
||||
)
|
||||
|
||||
// Finish the onboarding
|
||||
await nextButton.hover()
|
||||
await page.setViewportSize({ width: 1200, height: 1080 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// 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 expect(nextButton).toBeVisible()
|
||||
await nextButton.click()
|
||||
|
||||
// Test that the onboarding pane is gone
|
||||
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||
await expect.poll(() => page.url()).not.toContain('/onboarding')
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Onboarding redirects and code updating',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: '/export',
|
||||
},
|
||||
// Finish the onboarding
|
||||
await expect(nextButton).toBeVisible()
|
||||
await nextButton.click()
|
||||
|
||||
// Test that the onboarding pane is gone
|
||||
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||
await expect(page.url()).not.toContain('onboarding')
|
||||
})
|
||||
|
||||
test('Onboarding redirects and code updating', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
// Give some initial code, so we can test that it's cleared
|
||||
localStorage.setItem('persistCode', 'sigmaAllow = 15000')
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
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)
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }),
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Test that the redirect happened
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
)
|
||||
|
||||
// Test that you come back to this page when you refresh
|
||||
await page.reload()
|
||||
await expect(page.url().split(':3000').slice(-1)[0]).toBe(
|
||||
`/file/%2Fbrowser%2Fmain.kcl/onboarding/export`
|
||||
)
|
||||
|
||||
// Test that the onboarding pane loaded
|
||||
const title = page.locator('[data-testid="onboarding-content"]')
|
||||
await expect(title).toBeAttached()
|
||||
|
||||
// Test that the code changes when you advance to the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await expect(page.locator('.cm-content')).toHaveText('')
|
||||
|
||||
// Test that the code is not empty when you click on the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||
})
|
||||
|
||||
test('Onboarding code gets reset to demo on Interactive Numbers step', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip(
|
||||
process.platform === 'darwin',
|
||||
"Skip on macOS, because Playwright isn't behaving the same as the actual browser"
|
||||
)
|
||||
const u = await getUtils(page)
|
||||
const badCode = `// This is bad code we shouldn't see`
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings, badCode }) => {
|
||||
localStorage.setItem('persistCode', badCode)
|
||||
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(
|
||||
'Restarting onboarding on desktop takes one attempt',
|
||||
{
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'dismissed',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
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
|
||||
@ -443,8 +418,9 @@ test(
|
||||
const restartOnboardingButton = page.getByRole('button', {
|
||||
name: 'Reset onboarding',
|
||||
})
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
|
||||
const restartConfirmationButton = page.getByRole('button', {
|
||||
name: 'Make a new project',
|
||||
})
|
||||
const tutorialProjectIndicator = page
|
||||
.getByTestId('project-sidebar-toggle')
|
||||
.filter({ hasText: 'Tutorial Project 00' })
|
||||
@ -463,7 +439,7 @@ test(
|
||||
})
|
||||
|
||||
await test.step('Navigate into project', async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
@ -479,8 +455,8 @@ test(
|
||||
await helpMenuButton.click()
|
||||
await restartOnboardingButton.click()
|
||||
|
||||
await nextButton.hover()
|
||||
await nextButton.click()
|
||||
await expect(restartConfirmationButton).toBeVisible()
|
||||
await restartConfirmationButton.click()
|
||||
})
|
||||
|
||||
await test.step('Confirm that the onboarding has restarted', async () => {
|
||||
@ -504,9 +480,11 @@ test(
|
||||
|
||||
await restartOnboardingSettingsButton.click()
|
||||
// Since the code is empty, we should not see the confirmation dialog
|
||||
await expect(nextButton).not.toBeVisible()
|
||||
await expect(restartConfirmationButton).not.toBeVisible()
|
||||
await expect(tutorialProjectIndicator).toBeVisible()
|
||||
await expect(tutorialModalText).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
@ -1,49 +1,25 @@
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import { test, expect, AuthenticatedApp } from './fixtures/fixtureSetup'
|
||||
import { EditorFixture } from './fixtures/editorFixture'
|
||||
import { SceneFixture } from './fixtures/sceneFixture'
|
||||
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('verify extruding circle works', async ({
|
||||
context,
|
||||
homePage,
|
||||
cmdBar,
|
||||
editor,
|
||||
toolbar,
|
||||
scene,
|
||||
}) => {
|
||||
const file = await fs.readFile(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../../',
|
||||
'./src/wasm-lib/tests/executor/inputs/test-circle-extrude.kcl'
|
||||
),
|
||||
'utf-8'
|
||||
)
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
test(
|
||||
'verify extruding circle works',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ app, cmdBar, 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('test-circle-extrude.kcl')
|
||||
await app.initialise(file)
|
||||
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
||||
|
||||
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
||||
|
||||
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('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 () => {
|
||||
@ -51,7 +27,7 @@ test('verify extruding circle works', async ({
|
||||
const circleSnippet =
|
||||
'circle({ center = [318.33, 168.1], radius = 182.8 }, %)'
|
||||
await editor.expectState({
|
||||
activeLines: ["constsketch002=startSketchOn('XZ')"],
|
||||
activeLines: [],
|
||||
highlightedCode: circleSnippet,
|
||||
diagnostics: [],
|
||||
})
|
||||
@ -64,40 +40,39 @@ test('verify extruding circle works', async ({
|
||||
})
|
||||
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 toolbar.extrudeButton.click()
|
||||
await test.step('do extrude flow and check extrude code is added to editor', async () => {
|
||||
await toolbar.extrudeButton.click()
|
||||
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'distance',
|
||||
currentArgValue: '5',
|
||||
headerArguments: { Selection: '1 face', Distance: '' },
|
||||
highlightedHeaderArg: 'distance',
|
||||
commandName: 'Extrude',
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'distance',
|
||||
currentArgValue: '5',
|
||||
headerArguments: { Selection: '1 face', Distance: '' },
|
||||
highlightedHeaderArg: 'distance',
|
||||
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', () => {
|
||||
const _sketchOnAChamfer =
|
||||
(
|
||||
page: Page,
|
||||
app: AuthenticatedApp,
|
||||
editor: EditorFixture,
|
||||
toolbar: ToolbarFixture,
|
||||
scene: SceneFixture
|
||||
@ -149,7 +124,7 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
await clickChamfer()
|
||||
// timeout wait for engine animation is unavoidable
|
||||
await page.waitForTimeout(1000)
|
||||
await app.page.waitForTimeout(600)
|
||||
await editor.expectEditor.toContain(afterChamferSelectSnippet)
|
||||
})
|
||||
await test.step('make sure a basic sketch can be added', async () => {
|
||||
@ -160,9 +135,7 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
pixelDiff: 50,
|
||||
})
|
||||
await rectangle2ndClick()
|
||||
await editor.expectEditor.toContain(afterRectangle2ndClickSnippet, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await editor.expectEditor.toContain(afterRectangle2ndClickSnippet)
|
||||
})
|
||||
|
||||
await test.step('Clean up so that `_sketchOnAChamfer` util can be called again', async () => {
|
||||
@ -177,35 +150,24 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
})
|
||||
})
|
||||
}
|
||||
test('works on all edge selections and 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.kcl'
|
||||
),
|
||||
'utf-8'
|
||||
)
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
test(
|
||||
'works on all edge selections and 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.kcl')
|
||||
await app.initialise(file)
|
||||
|
||||
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||
const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene)
|
||||
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 570, y: 220 },
|
||||
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
||||
cameraTarget: { x: -150, y: -4500, z: -80 },
|
||||
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 570, y: 220 },
|
||||
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 = [
|
||||
seg01,
|
||||
getNextAdjacentEdge(yo),
|
||||
@ -213,9 +175,10 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
getOppositeEdge(seg01)
|
||||
]}, %)`,
|
||||
|
||||
afterChamferSelectSnippet: 'sketch002 = startSketchOn(extrude001, seg03)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
afterChamferSelectSnippet:
|
||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
@ -226,13 +189,13 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
})
|
||||
})
|
||||
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 690, y: 250 },
|
||||
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
||||
cameraTarget: { x: -150, y: -4500, z: -80 },
|
||||
beforeChamferSnippet: `angledLine([
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 690, y: 250 },
|
||||
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
||||
cameraTarget: { x: -150, y: -4500, z: -80 },
|
||||
beforeChamferSnippet: `angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
217.26
|
||||
], %, $seg01)chamfer({
|
||||
@ -244,9 +207,10 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
]
|
||||
}, %)`,
|
||||
|
||||
afterChamferSelectSnippet: 'sketch003 = startSketchOn(extrude001, seg04)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
afterChamferSelectSnippet:
|
||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
@ -257,21 +221,22 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
], %, $rectangleSegmentC002)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
})
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 677, y: 87 },
|
||||
cameraPos: { x: -6200, y: 1500, z: 6200 },
|
||||
cameraTarget: { x: 8300, y: 1100, z: 4800 },
|
||||
beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({
|
||||
})
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 677, y: 87 },
|
||||
cameraPos: { x: -6200, y: 1500, z: 6200 },
|
||||
cameraTarget: { x: 8300, y: 1100, z: 4800 },
|
||||
beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({
|
||||
length = 30,
|
||||
tags = [
|
||||
getNextAdjacentEdge(yo),
|
||||
getNextAdjacentEdge(seg02)
|
||||
]
|
||||
}, %)`,
|
||||
afterChamferSelectSnippet: 'sketch003 = startSketchOn(extrude001, seg04)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([75.8, 317.2], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
afterChamferSelectSnippet:
|
||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
@ -282,19 +247,20 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
], %, $rectangleSegmentC002)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
})
|
||||
/// last one
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 620, y: 300 },
|
||||
cameraPos: { x: -1100, y: -7700, z: 1600 },
|
||||
cameraTarget: { x: 1450, y: 670, z: 4000 },
|
||||
beforeChamferSnippet: `chamfer({
|
||||
})
|
||||
/// last one
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 620, y: 300 },
|
||||
cameraPos: { x: -1100, y: -7700, z: 1600 },
|
||||
cameraTarget: { x: 1450, y: 670, z: 4000 },
|
||||
beforeChamferSnippet: `chamfer({
|
||||
length = 30,
|
||||
tags = [getNextAdjacentEdge(yo)]
|
||||
}, %)`,
|
||||
afterChamferSelectSnippet: 'sketch005 = startSketchOn(extrude001, seg06)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||
afterChamferSelectSnippet:
|
||||
'sketch005 = startSketchOn(extrude001, seg06)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005) - 90,
|
||||
@ -306,11 +272,11 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
], %, $rectangleSegmentC004)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('verify at the end of the test that final code is what is expected', async () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
await test.step('verify at the end of the test that final code is what is expected', async () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|
||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
@ -339,7 +305,7 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
tags = [getNextAdjacentEdge(yo)]
|
||||
}, %, $seg06)
|
||||
sketch005 = startSketchOn(extrude001, seg06)
|
||||
|> startProfileAt([-23.43,19.69], %)
|
||||
|> startProfileAt([-23.43, 19.69], %)
|
||||
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005) - 90,
|
||||
@ -352,7 +318,7 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch004 = startSketchOn(extrude001, seg05)
|
||||
|> startProfileAt([82.57,322.96], %)
|
||||
|> startProfileAt([82.57, 322.96], %)
|
||||
|> angledLine([0, 11.16], %, $rectangleSegmentA004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004) - 90,
|
||||
@ -365,7 +331,7 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch003 = startSketchOn(extrude001, seg04)
|
||||
|> startProfileAt([-209.64,255.28], %)
|
||||
|> startProfileAt([-209.64, 255.28], %)
|
||||
|> angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
@ -378,7 +344,7 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch002 = startSketchOn(extrude001, seg03)
|
||||
|> startProfileAt([205.96,254.59], %)
|
||||
|> startProfileAt([205.96, 254.59], %)
|
||||
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
@ -391,50 +357,43 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> 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)
|
||||
|
||||
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()
|
||||
const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene)
|
||||
|
||||
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 570, y: 220 },
|
||||
cameraPos: { x: 16020, y: -2000, z: 10500 },
|
||||
cameraTarget: { x: -150, y: -4500, z: -80 },
|
||||
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 570, y: 220 },
|
||||
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=[
|
||||
seg01,
|
||||
getNextAdjacentEdge(yo),
|
||||
getNextAdjacentEdge(seg02),
|
||||
getOppositeEdge(seg01)
|
||||
]}, extrude001)`,
|
||||
beforeChamferSnippetEnd: '}, extrude001)',
|
||||
afterChamferSelectSnippet: 'sketch002 = startSketchOn(extrude001, seg03)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
beforeChamferSnippetEnd: '}, extrude001)',
|
||||
afterChamferSelectSnippet:
|
||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
@ -445,9 +404,9 @@ test.describe('verify sketch on chamfer works', () => {
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`,
|
||||
})
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
})
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([75.8, 317.2], %)
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
@ -487,56 +446,50 @@ sketch002 = startSketchOn(extrude001, seg03)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
})
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||
page,
|
||||
homePage,
|
||||
app,
|
||||
editor,
|
||||
toolbar,
|
||||
scene,
|
||||
}) => {
|
||||
const viewPortSize = { width: 1200, height: 500 }
|
||||
|
||||
await page.setBodyDimensions(viewPortSize)
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Constants and locators
|
||||
// These are mappings from screenspace to KCL coordinates,
|
||||
// until we merge in our coordinate system helpers
|
||||
const xzPlane = [
|
||||
viewPortSize.width * 0.65,
|
||||
viewPortSize.height * 0.3,
|
||||
app.viewPortSize.width * 0.65,
|
||||
app.viewPortSize.height * 0.3,
|
||||
] as const
|
||||
const originSloppy = {
|
||||
screen: [
|
||||
viewPortSize.width / 2 + 3, // 3px off the center of the screen
|
||||
viewPortSize.height / 2,
|
||||
app.viewPortSize.width / 2 + 3, // 3px off the center of the screen
|
||||
app.viewPortSize.height / 2,
|
||||
],
|
||||
kcl: [0, 0],
|
||||
} as const
|
||||
const xAxisSloppy = {
|
||||
screen: [
|
||||
viewPortSize.width * 0.75,
|
||||
viewPortSize.height / 2 - 3, // 3px off the X-axis
|
||||
app.viewPortSize.width * 0.75,
|
||||
app.viewPortSize.height / 2 - 3, // 3px off the X-axis
|
||||
],
|
||||
kcl: [20.34, 0],
|
||||
kcl: [16.95, 0],
|
||||
} as const
|
||||
const offYAxis = {
|
||||
screen: [
|
||||
viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range
|
||||
viewPortSize.height * 0.3,
|
||||
app.viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range
|
||||
app.viewPortSize.height * 0.3,
|
||||
],
|
||||
kcl: [8.14, 6.78],
|
||||
kcl: [6.78, 6.78],
|
||||
} as const
|
||||
const yAxisSloppy = {
|
||||
screen: [
|
||||
viewPortSize.width / 2 + 5, // 5px off the Y-axis
|
||||
viewPortSize.height * 0.3,
|
||||
app.viewPortSize.width / 2 + 5, // 5px off the Y-axis
|
||||
app.viewPortSize.height * 0.3,
|
||||
],
|
||||
kcl: [0, 6.78],
|
||||
} as const
|
||||
@ -557,13 +510,15 @@ test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
|
||||
}
|
||||
|
||||
await app.initialise()
|
||||
|
||||
await test.step(`Start a sketch on the XZ plane`, async () => {
|
||||
await editor.closePane()
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
await moveToXzPlane()
|
||||
await clickOnXzPlane()
|
||||
// timeout wait for engine animation is unavoidable
|
||||
await page.waitForTimeout(600)
|
||||
await app.page.waitForTimeout(600)
|
||||
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 () => {
|
||||
@ -598,15 +553,11 @@ test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||
})
|
||||
|
||||
test(`Verify user can double-click to edit a sketch`, async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
app,
|
||||
editor,
|
||||
toolbar,
|
||||
scene,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
const initialCode = `closedSketch = startSketchOn('XZ')
|
||||
|> circle({ center = [8, 5], radius = 2 }, %)
|
||||
openSketch = startSketchOn('XY')
|
||||
@ -615,24 +566,15 @@ openSketch = startSketchOn('XY')
|
||||
|> xLine(5, %)
|
||||
|> tangentialArcTo([10, 0], %)
|
||||
`
|
||||
const viewPortSize = { width: 1000, height: 500 }
|
||||
await page.setBodyDimensions(viewPortSize)
|
||||
|
||||
await context.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, initialCode)
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.waitForTimeout(1000)
|
||||
await app.initialise(initialCode)
|
||||
|
||||
const pointInsideCircle = {
|
||||
x: viewPortSize.width * 0.63,
|
||||
y: viewPortSize.height * 0.5,
|
||||
x: app.viewPortSize.width * 0.63,
|
||||
y: app.viewPortSize.height * 0.5,
|
||||
}
|
||||
const pointOnPathAfterSketching = {
|
||||
x: viewPortSize.width * 0.65,
|
||||
y: viewPortSize.height * 0.5,
|
||||
x: app.viewPortSize.width * 0.58,
|
||||
y: app.viewPortSize.height * 0.5,
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] =
|
||||
@ -665,59 +607,41 @@ openSketch = startSketchOn('XY')
|
||||
diagnostics: [],
|
||||
})
|
||||
})
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
await exitSketch()
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Drag the sketch line out of the axis view which blocks the click
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: {
|
||||
x: viewPortSize.width * 0.7,
|
||||
y: viewPortSize.height * 0.5,
|
||||
},
|
||||
targetPosition: {
|
||||
x: viewPortSize.width * 0.7,
|
||||
y: viewPortSize.height * 0.4,
|
||||
},
|
||||
})
|
||||
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await test.step(`Double-click on the open sketch`, async () => {
|
||||
await moveToOpenPath()
|
||||
await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15)
|
||||
// There is a full execution after exiting sketch that clears the scene.
|
||||
await page.waitForTimeout(500)
|
||||
await app.page.waitForTimeout(500)
|
||||
await dblClickOpenPath()
|
||||
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||
// Wait for enter sketch mode to complete
|
||||
await page.waitForTimeout(500)
|
||||
await app.page.waitForTimeout(500)
|
||||
await editor.expectState({
|
||||
activeLines: [`|>tangentialArcTo([10,0],%)`],
|
||||
highlightedCode: 'tangentialArcTo([10,0],%)',
|
||||
activeLines: [`|>xLine(5,%)`],
|
||||
highlightedCode: 'xLine(5,%)',
|
||||
diagnostics: [],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test(`Offset plane point-and-click`, async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
app,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
await app.initialise()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 700, y: 150 }
|
||||
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const expectedOutput = `plane001 = offsetPlane('XZ', 5)`
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await test.step(`Look for the blue of the XZ plane`, async () => {
|
||||
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
||||
})
|
||||
@ -760,30 +684,20 @@ const loftPointAndClickCases = [
|
||||
]
|
||||
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||
test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
|
||||
context,
|
||||
homePage,
|
||||
app,
|
||||
page,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||
`
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await context.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, initialCode)
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.waitForTimeout(1000)
|
||||
await app.initialise(initialCode)
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 575, y: 200 }
|
||||
@ -802,7 +716,7 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||
await clickOnSketch1()
|
||||
await page.keyboard.down('Shift')
|
||||
await clickOnSketch2()
|
||||
await page.waitForTimeout(500)
|
||||
await app.page.waitForTimeout(500)
|
||||
await page.keyboard.up('Shift')
|
||||
}
|
||||
|
||||
@ -854,3 +768,168 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const shellPointAndClickCapCases = [
|
||||
{ shouldPreselect: true },
|
||||
{ shouldPreselect: false },
|
||||
]
|
||||
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
||||
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
|
||||
app,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
extrude001 = extrude(30, sketch001)
|
||||
`
|
||||
await app.initialise(initialCode)
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 575, y: 200 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const shellDeclaration =
|
||||
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
|
||||
})
|
||||
|
||||
if (!shouldPreselect) {
|
||||
await test.step(`Go through the command bar flow without preselected faces`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Thickness: '',
|
||||
},
|
||||
highlightedHeaderArg: 'selection',
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await clickOnCap()
|
||||
await app.page.waitForTimeout(500)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Selection: '1 cap',
|
||||
Thickness: '5',
|
||||
},
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
} else {
|
||||
await test.step(`Preselect the cap`, async () => {
|
||||
await clickOnCap()
|
||||
await app.page.waitForTimeout(500)
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Selection: '1 cap',
|
||||
Thickness: '5',
|
||||
},
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
}
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(shellDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [shellDeclaration],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await scene.expectPixelColor([146, 146, 146], testPoint, 15)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Shell point-and-click wall', async ({
|
||||
app,
|
||||
page,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-20, 20], %)
|
||||
|> xLine(40, %)
|
||||
|> yLine(-60, %)
|
||||
|> xLine(-40, %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(40, sketch001)
|
||||
`
|
||||
await app.initialise(initialCode)
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 580, y: 180 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
|
||||
const mutatedCode = 'xLine(-40, %, $seg01)'
|
||||
const shellDeclaration =
|
||||
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
|
||||
const formattedOutLastLine = '}, extrude001)'
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Thickness: '',
|
||||
},
|
||||
highlightedHeaderArg: 'selection',
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await clickOnCap()
|
||||
await page.keyboard.down('Shift')
|
||||
await clickOnWall()
|
||||
await app.page.waitForTimeout(500)
|
||||
await page.keyboard.up('Shift')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Selection: '1 cap, 1 face',
|
||||
Thickness: '5',
|
||||
},
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(mutatedCode)
|
||||
await editor.expectEditor.toContain(shellDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [formattedOutLastLine],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,37 +1,46 @@
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import path from 'path'
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import { join } from 'path'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { getUtils, executorInputPath } from './test-utils'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Regression tests', () => {
|
||||
// bugs we found that don't fit neatly into other categories
|
||||
test('bad model has inline error #3251', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('bad model has inline error #3251', async ({ page }) => {
|
||||
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
||||
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
||||
// Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch2 = startSketchOn("XY")
|
||||
sketch001 = startSketchAt([-0, -0])
|
||||
|> line([0, 0], %)
|
||||
|> line([-4.84, -5.29], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
sketch001 = startSketchAt([-0, -0])
|
||||
|> line([0, 0], %)
|
||||
|> line([-4.84, -5.29], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
@ -47,7 +56,6 @@ test.describe('Regression tests', () => {
|
||||
})
|
||||
test('user should not have to press down twice in cmdbar', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
||||
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
||||
@ -56,38 +64,26 @@ test.describe('Regression tests', () => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([82.33, 238.21], %)
|
||||
|> angledLine([0, 288.63], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
197.97
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
`sketch2 = startSketchOn("XY")
|
||||
sketch001 = startSketchAt([-0, -0])
|
||||
|> line([0, 0], %)
|
||||
|> line([-4.84, -5.29], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await page.goto('/')
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await test.step('Check arrow down works', async () => {
|
||||
await page.getByTestId('command-bar-open-button').hover()
|
||||
await page.getByTestId('command-bar-open-button').click()
|
||||
|
||||
const floppy = page.getByRole('option', {
|
||||
name: 'floppy disk arrow Export',
|
||||
})
|
||||
|
||||
await floppy.click()
|
||||
await page
|
||||
.getByRole('option', { name: 'floppy disk arrow Export' })
|
||||
.click()
|
||||
|
||||
// press arrow down key twice
|
||||
await page.keyboard.press('ArrowDown')
|
||||
@ -119,22 +115,21 @@ extrude001 = extrude(50, sketch001)
|
||||
)
|
||||
})
|
||||
})
|
||||
test('executes on load', async ({ page, homePage }) => {
|
||||
test('executes on load', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([-6.95, 4.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -14.93], %)
|
||||
|> line([-23.44, 0.52], %)`
|
||||
|> startProfileAt([-6.95, 4.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -14.93], %)
|
||||
|> line([-23.44, 0.52], %)`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// expand variables section
|
||||
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||
@ -153,15 +148,14 @@ extrude001 = extrude(50, sketch001)
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('re-executes', async ({ page, homePage }) => {
|
||||
test('re-executes', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', `myVar = 5`)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||
await variablesTabButton.click()
|
||||
@ -180,33 +174,32 @@ extrude001 = extrude(50, sketch001)
|
||||
page.locator('.pretty-json-container >> text=myVar:67')
|
||||
).toBeVisible()
|
||||
})
|
||||
test('ProgramMemory can be serialised', async ({ page, homePage }) => {
|
||||
test('ProgramMemory can be serialised', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 1], %)
|
||||
|> line([1, 0], %)
|
||||
|> line([0, -1], %)
|
||||
|> close(%)
|
||||
|> extrude(1, %)
|
||||
|> patternLinear3d({
|
||||
axis: [1, 0, 1],
|
||||
repetitions: 3,
|
||||
distance: 6
|
||||
}, %)`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 1], %)
|
||||
|> line([1, 0], %)
|
||||
|> line([0, -1], %)
|
||||
|> close(%)
|
||||
|> extrude(1, %)
|
||||
|> patternLinear3d({
|
||||
axis: [1, 0, 1],
|
||||
repetitions: 3,
|
||||
distance: 6
|
||||
}, %)`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
const messages: string[] = []
|
||||
|
||||
// Listen for all console events and push the message text to an array
|
||||
page.on('console', (message) => messages.push(message.text()))
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -219,26 +212,19 @@ extrude001 = extrude(50, sketch001)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 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,
|
||||
}) => {
|
||||
test('ensure the Zoo logo is not a link in browser app', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const zooLogo = page.locator('[data-testid="app-logo"]')
|
||||
// Make sure it's not a link
|
||||
await expect(zooLogo).not.toHaveAttribute('href')
|
||||
})
|
||||
|
||||
test(
|
||||
'Position _ Is Out Of Range... regression test',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ context, page, homePage }) => {
|
||||
async ({ page }) => {
|
||||
// SKip on windows, its being weird.
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
@ -247,26 +233,25 @@ extrude001 = extrude(50, sketch001)
|
||||
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await context.addInitScript(async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|>
|
||||
|
||||
example = extrude(5, exampleSketch)
|
||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|>
|
||||
|
||||
example = extrude(5, exampleSketch)
|
||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||
)
|
||||
})
|
||||
|
||||
await expect(async () => {
|
||||
await homePage.goToModelingScene()
|
||||
await page.goto('/')
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||
timeout: 1_000,
|
||||
@ -308,12 +293,12 @@ extrude001 = extrude(50, sketch001)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|
||||
thing: "blah"`)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|
||||
thing: "blah"`)
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
}
|
||||
@ -321,7 +306,6 @@ extrude001 = extrude(50, sketch001)
|
||||
|
||||
test('when engine fails export we handle the failure and alert the user', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(
|
||||
@ -332,10 +316,9 @@ extrude001 = extrude(50, sketch001)
|
||||
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
|
||||
)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -391,6 +374,7 @@ extrude001 = extrude(50, sketch001)
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.clearCommandLogs()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
@ -424,7 +408,7 @@ extrude001 = extrude(50, sketch001)
|
||||
test(
|
||||
'ensure you can not export while an export is already going',
|
||||
{ tag: ['@skipLinux', '@skipWin'] },
|
||||
async ({ page, homePage }) => {
|
||||
async ({ page }) => {
|
||||
// This is being weird on ubuntu and windows.
|
||||
test.skip(
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
@ -444,10 +428,9 @@ extrude001 = extrude(50, sketch001)
|
||||
}
|
||||
)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -517,17 +500,20 @@ extrude001 = extrude(50, sketch001)
|
||||
test(
|
||||
`Network health indicator only appears in modeling view`,
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
path.join(bracketDir, 'main.kcl')
|
||||
)
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Locators
|
||||
@ -553,12 +539,13 @@ extrude001 = extrude(50, sketch001)
|
||||
await u.waitForPageLoad()
|
||||
await expect(networkHealthIndicator).toContainText('Connected')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(`View gizmo stays visible even when zoomed out all the way`, async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -574,9 +561,8 @@ extrude001 = extrude(50, sketch001)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', '')
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
|
||||
@ -1,20 +1,27 @@
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import { test as test2, expect as expect2 } from './fixtures/fixtureSetup'
|
||||
|
||||
import {
|
||||
getMovementUtils,
|
||||
getUtils,
|
||||
PERSIST_MODELING_CONTEXT,
|
||||
setup,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
import { uuidv4, roundOff } from 'lib/utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Sketch tests', () => {
|
||||
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
|
||||
page,
|
||||
context,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
const selectionsSnippets = {
|
||||
@ -28,53 +35,53 @@ test.describe('Sketch tests', () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`
|
||||
width = 20
|
||||
height = 10
|
||||
thickness = 5
|
||||
screwRadius = 3
|
||||
wireRadius = 2
|
||||
wireOffset = 0.5
|
||||
|
||||
screwHole = startSketchOn('XY')
|
||||
${startProfileAt1}
|
||||
|> arc({
|
||||
radius = screwRadius,
|
||||
angle_start = 0,
|
||||
angle_end = 360
|
||||
}, %)
|
||||
|
||||
part001 = startSketchOn('XY')
|
||||
${startProfileAt2}
|
||||
|> xLine(width * .5, %)
|
||||
|> yLine(height, %)
|
||||
|> xLine(-width * .5, %)
|
||||
|> close(%)
|
||||
|> hole(screwHole, %)
|
||||
|> extrude(thickness, %)
|
||||
|
||||
part002 = startSketchOn('-XZ')
|
||||
${startProfileAt3}
|
||||
|> xLine(width / 4, %)
|
||||
|> tangentialArcTo([width / 2, 0], %)
|
||||
|> xLine(-width / 4 + wireRadius, %)
|
||||
|> yLine(wireOffset, %)
|
||||
|> arc({
|
||||
radius = wireRadius,
|
||||
angle_start = 0,
|
||||
angle_end = 180
|
||||
}, %)
|
||||
|> yLine(-wireOffset, %)
|
||||
|> xLine(-width / 4, %)
|
||||
|> close(%)
|
||||
|> extrude(-height, %)
|
||||
`
|
||||
width = 20
|
||||
height = 10
|
||||
thickness = 5
|
||||
screwRadius = 3
|
||||
wireRadius = 2
|
||||
wireOffset = 0.5
|
||||
|
||||
screwHole = startSketchOn('XY')
|
||||
${startProfileAt1}
|
||||
|> arc({
|
||||
radius = screwRadius,
|
||||
angle_start = 0,
|
||||
angle_end = 360
|
||||
}, %)
|
||||
|
||||
part001 = startSketchOn('XY')
|
||||
${startProfileAt2}
|
||||
|> xLine(width * .5, %)
|
||||
|> yLine(height, %)
|
||||
|> xLine(-width * .5, %)
|
||||
|> close(%)
|
||||
|> hole(screwHole, %)
|
||||
|> extrude(thickness, %)
|
||||
|
||||
part002 = startSketchOn('-XZ')
|
||||
${startProfileAt3}
|
||||
|> xLine(width / 4, %)
|
||||
|> tangentialArcTo([width / 2, 0], %)
|
||||
|> xLine(-width / 4 + wireRadius, %)
|
||||
|> yLine(wireOffset, %)
|
||||
|> arc({
|
||||
radius = wireRadius,
|
||||
angle_start = 0,
|
||||
angle_end = 180
|
||||
}, %)
|
||||
|> yLine(-wireOffset, %)
|
||||
|> xLine(-width / 4, %)
|
||||
|> close(%)
|
||||
|> extrude(-height, %)
|
||||
`
|
||||
)
|
||||
},
|
||||
selectionsSnippets
|
||||
)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -101,22 +108,24 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
test('Can delete most of a sketch and the line tool will still work', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> xLine(12.73, %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)`
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> xLine(12.73, %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)`
|
||||
)
|
||||
})
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await expect(async () => {
|
||||
await page.mouse.click(700, 200)
|
||||
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
@ -133,7 +142,8 @@ test.describe('Sketch tests', () => {
|
||||
await page.keyboard.press('Home')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
await u.openDebugPanel()
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -141,31 +151,26 @@ test.describe('Sketch tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(async () => {
|
||||
await page.mouse.move(700, 200, { steps: 25 })
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect
|
||||
.poll(u.crushKclCodeIntoOneLineAndThenMaybeSome, { timeout: 1000 })
|
||||
.toBe(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61,-14.01], %)
|
||||
|> yLine(15.95, %)
|
||||
`
|
||||
.replaceAll(' ', '')
|
||||
.replaceAll('\n', '')
|
||||
)
|
||||
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
|> yLine(12.34, %)
|
||||
|
||||
`)
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
})
|
||||
|
||||
test('Can exit selection of face', async ({ page, homePage }) => {
|
||||
test('Can exit selection of face', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', ``)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await expect(
|
||||
@ -182,7 +187,6 @@ test.describe('Sketch tests', () => {
|
||||
test.describe('Can edit segments by dragging their handles', () => {
|
||||
const doEditSegmentsByDraggingHandle = async (
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
openPanes: string[]
|
||||
) => {
|
||||
// Load the app with the code panes
|
||||
@ -198,8 +202,9 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -313,7 +318,7 @@ test.describe('Sketch tests', () => {
|
||||
|> line([1.97, 2.06], %)
|
||||
|> close(%)`)
|
||||
}
|
||||
test('code pane open at start-handles', async ({ page, homePage }) => {
|
||||
test('code pane open at start-handles', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -326,10 +331,10 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
)
|
||||
})
|
||||
await doEditSegmentsByDraggingHandle(page, homePage, ['code'])
|
||||
await doEditSegmentsByDraggingHandle(page, ['code'])
|
||||
})
|
||||
|
||||
test('code pane closed at start-handles', async ({ page, homePage }) => {
|
||||
test('code pane closed at start-handles', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async (persistModelingContext) => {
|
||||
localStorage.setItem(
|
||||
@ -337,26 +342,25 @@ test.describe('Sketch tests', () => {
|
||||
JSON.stringify({ openPanes: [] })
|
||||
)
|
||||
}, PERSIST_MODELING_CONTEXT)
|
||||
await doEditSegmentsByDraggingHandle(page, homePage, [])
|
||||
await doEditSegmentsByDraggingHandle(page, [])
|
||||
})
|
||||
})
|
||||
|
||||
test('Can edit a circle center and radius by dragging its handles', async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [4.61, -5.01], radius = 8 }, %)`
|
||||
|> circle({ center = [4.61, -5.01], radius = 8 }, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -395,7 +399,6 @@ test.describe('Sketch tests', () => {
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(1)
|
||||
@ -406,9 +409,7 @@ test.describe('Sketch tests', () => {
|
||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await editor.expectEditor.not.toContain(prevContent)
|
||||
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
})
|
||||
|
||||
@ -421,36 +422,35 @@ test.describe('Sketch tests', () => {
|
||||
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||
targetPosition: { x: lineEnd.x + dragPX * 2, y: lineEnd.y + dragPX },
|
||||
})
|
||||
await editor.expectEditor.not.toContain(prevContent)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
})
|
||||
|
||||
// expect the code to have changed
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [7.26, -2.37], radius = 11.44 }, %)`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [7.26, -2.37], radius = 11.44 }, %)
|
||||
`)
|
||||
})
|
||||
test('Can edit a sketch that has been extruded in the same pipe', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -504,11 +504,11 @@ test.describe('Sketch tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: lineEnd.x - 15, y: lineEnd.y },
|
||||
targetPosition: { x: lineEnd.x, y: lineEnd.y + 15 },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
@ -517,8 +517,8 @@ test.describe('Sketch tests', () => {
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
|
||||
targetPosition: {
|
||||
x: tangentEnd.x,
|
||||
y: tangentEnd.y - 15,
|
||||
x: tangentEnd.x + dragPX,
|
||||
y: tangentEnd.y + dragPX,
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
@ -527,33 +527,33 @@ test.describe('Sketch tests', () => {
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([12.68, -1.09], %)
|
||||
|> tangentialArcTo([24.89, 0.68], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([15.39, -2.78], %)
|
||||
|> tangentialArcTo([27.6, -3.05], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
})
|
||||
|
||||
test('Can edit a sketch that has been revolved in the same pipe', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> revolve({ axis = "X",}, %)`
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> revolve({ axis = "X",}, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -629,20 +629,19 @@ test.describe('Sketch tests', () => {
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([6.44, -12.07], %)
|
||||
|> line([14.72, 1.97], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> line([1.97, 2.06], %)
|
||||
|> close(%)
|
||||
|> revolve({ axis = "X" }, %)`)
|
||||
|> startProfileAt([6.44, -12.07], %)
|
||||
|> line([14.72, 1.97], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> line([1.97, 2.06], %)
|
||||
|> close(%)
|
||||
|> revolve({ axis = "X" }, %)`)
|
||||
})
|
||||
test('Can add multiple sketches', async ({ page, homePage }) => {
|
||||
test('Can add multiple sketches', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setBodyDimensions(viewportSize)
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||
@ -737,8 +736,9 @@ test.describe('Sketch tests', () => {
|
||||
scale = 1
|
||||
) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const code = `sketch001 = startSketchOn('-XZ')
|
||||
@ -820,39 +820,36 @@ test.describe('Sketch tests', () => {
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.removeCurrentCode()
|
||||
}
|
||||
test('[0, 100, 100]', async ({ page, homePage }) => {
|
||||
await homePage.goToModelingScene()
|
||||
test('[0, 100, 100]', async ({ page }) => {
|
||||
await doSnapAtDifferentScales(page, [0, 100, 100], 0.01)
|
||||
})
|
||||
|
||||
test('[0, 10000, 10000]', async ({ page, homePage }) => {
|
||||
await homePage.goToModelingScene()
|
||||
test('[0, 10000, 10000]', async ({ page }) => {
|
||||
await doSnapAtDifferentScales(page, [0, 10000, 10000])
|
||||
})
|
||||
})
|
||||
test('exiting a close extrude, has the extrude button enabled ready to go', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-0.45, 0.87], %)
|
||||
|> line([1.32, 0.38], %)
|
||||
|> line([1.02, -1.32], %, $seg01)
|
||||
|> line([-1.01, -0.77], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
|> startProfileAt([-0.45, 0.87], %)
|
||||
|> line([1.32, 0.38], %)
|
||||
|> line([1.02, -1.32], %, $seg01)
|
||||
|> line([-1.01, -0.77], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -888,30 +885,27 @@ test.describe('Sketch tests', () => {
|
||||
timeout: 10_000,
|
||||
})
|
||||
})
|
||||
test("Existing sketch with bad code delete user's code", async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test("Existing sketch with bad code delete user's code", async ({ page }) => {
|
||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-0.45, 0.87], %)
|
||||
|> line([1.32, 0.38], %)
|
||||
|> line([1.02, -1.32], %, $seg01)
|
||||
|> line([-1.01, -0.77], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)
|
||||
`
|
||||
|> startProfileAt([-0.45, 0.87], %)
|
||||
|> line([1.32, 0.38], %)
|
||||
|> line([1.02, -1.32], %, $seg01)
|
||||
|> line([-1.01, -0.77], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
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 u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -937,16 +931,16 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
await expect((await u.codeLocator.innerText()).replace(/\s/g, '')).toBe(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-0.45, 0.87], %)
|
||||
|> line([1.32, 0.38], %)
|
||||
|> line([1.02, -1.32], %, $seg01)
|
||||
|> line([-1.01, -0.77], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)
|
||||
sketch002 = startSketchOn(extrude001, 'END')
|
||||
|>
|
||||
`.replace(/\s/g, '')
|
||||
|> startProfileAt([-0.45, 0.87], %)
|
||||
|> line([1.32, 0.38], %)
|
||||
|> line([1.02, -1.32], %, $seg01)
|
||||
|> line([-1.01, -0.77], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)
|
||||
sketch002 = startSketchOn(extrude001, 'END')
|
||||
|>
|
||||
`.replace(/\s/g, '')
|
||||
)
|
||||
})
|
||||
|
||||
@ -1055,8 +1049,12 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
test('empty-scene default-planes act as expected', async ({
|
||||
page,
|
||||
homePage,
|
||||
browserName,
|
||||
}) => {
|
||||
test.skip(
|
||||
browserName === 'webkit',
|
||||
'Skip on Safari until `window.tearDown` is working there'
|
||||
)
|
||||
/**
|
||||
* Tests the following things
|
||||
* 1) The the planes are there on load because the scene is empty
|
||||
@ -1069,7 +1067,9 @@ test.describe('Sketch tests', () => {
|
||||
*/
|
||||
|
||||
const u = await getUtils(page)
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -1092,11 +1092,11 @@ test.describe('Sketch tests', () => {
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await u.codeLocator.fill(`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
`)
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
`)
|
||||
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
@ -1117,7 +1117,7 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
// click start Sketch
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 50 })
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 5 })
|
||||
const hoveredColor: [number, number, number] = [93, 93, 127]
|
||||
// now that we're expecting the user to select a plan, it does respond to hover
|
||||
await expect
|
||||
@ -1131,19 +1131,21 @@ test.describe('Sketch tests', () => {
|
||||
await page.waitForTimeout(200)
|
||||
await page.mouse.click(XYPlanePoint.x + 50, XYPlanePoint.y + 50)
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([11.8, 9.09], %)
|
||||
|> line([3.39, -3.39], %)
|
||||
`)
|
||||
|> startProfileAt([11.8, 9.09], %)
|
||||
|> line([3.39, -3.39], %)
|
||||
`)
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([11.8, 9.09], %)
|
||||
|> line([3.39, -3.39], %)
|
||||
`
|
||||
|> startProfileAt([11.8, 9.09], %)
|
||||
|> line([3.39, -3.39], %)
|
||||
`
|
||||
)
|
||||
})
|
||||
await page.reload()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -1155,34 +1157,41 @@ test.describe('Sketch tests', () => {
|
||||
).toBeLessThan(3)
|
||||
})
|
||||
|
||||
test('Can attempt to sketch on revolved face', async ({ page, homePage }) => {
|
||||
test('Can attempt to sketch on revolved face', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.skip(
|
||||
browserName === 'webkit',
|
||||
'Skip on Safari until `window.tearDown` is working there'
|
||||
)
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`lugHeadLength = 0.25
|
||||
lugDiameter = 0.5
|
||||
lugLength = 2
|
||||
|
||||
fn lug = (origin, length, diameter, plane) => {
|
||||
lugSketch = startSketchOn(plane)
|
||||
|> startProfileAt([origin[0] + lugDiameter / 2, origin[1]], %)
|
||||
|> angledLineOfYLength({ angle = 60, length = lugHeadLength }, %)
|
||||
|> xLineTo(0 + .001, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|> revolve({ axis = "Y" }, %)
|
||||
|
||||
return lugSketch
|
||||
}
|
||||
|
||||
lug([0, 0], 10, .5, "XY")`
|
||||
lugDiameter = 0.5
|
||||
lugLength = 2
|
||||
|
||||
fn lug = (origin, length, diameter, plane) => {
|
||||
lugSketch = startSketchOn(plane)
|
||||
|> startProfileAt([origin[0] + lugDiameter / 2, origin[1]], %)
|
||||
|> angledLineOfYLength({ angle = 60, length = lugHeadLength }, %)
|
||||
|> xLineTo(0 + .001, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|> revolve({ axis = "Y" }, %)
|
||||
|
||||
return lugSketch
|
||||
}
|
||||
|
||||
lug([0, 0], 10, .5, "XY")`
|
||||
)
|
||||
})
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -1214,10 +1223,9 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
test('Can sketch on face when user defined function was used in the sketch', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Checking for a regression that performs a sketch when a user defined function
|
||||
// is declared at the top of the file and used in the sketch that is being drawn on.
|
||||
@ -1227,51 +1235,51 @@ test.describe('Sketch tests', () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`fn in2mm = (inches) => {
|
||||
return inches * 25.4
|
||||
}
|
||||
|
||||
const railTop = in2mm(.748)
|
||||
const railSide = in2mm(.024)
|
||||
const railBaseWidth = in2mm(.612)
|
||||
const railWideWidth = in2mm(.835)
|
||||
const railBaseLength = in2mm(.200)
|
||||
const railClampable = in2mm(.200)
|
||||
|
||||
const rail = startSketchOn('XZ')
|
||||
|> startProfileAt([
|
||||
-railTop / 2,
|
||||
railClampable + railBaseLength
|
||||
], %)
|
||||
|> lineTo([
|
||||
railTop / 2,
|
||||
railClampable + railBaseLength
|
||||
], %)
|
||||
|> lineTo([
|
||||
railWideWidth / 2,
|
||||
railClampable / 2 + railBaseLength
|
||||
], %, $seg01)
|
||||
|> lineTo([railTop / 2, railBaseLength], %)
|
||||
|> lineTo([railBaseWidth / 2, railBaseLength], %)
|
||||
|> lineTo([railBaseWidth / 2, 0], %)
|
||||
|> lineTo([-railBaseWidth / 2, 0], %)
|
||||
|> lineTo([-railBaseWidth / 2, railBaseLength], %)
|
||||
|> lineTo([-railTop / 2, railBaseLength], %)
|
||||
|> lineTo([
|
||||
-railWideWidth / 2,
|
||||
railClampable / 2 + railBaseLength
|
||||
], %)
|
||||
|> lineTo([
|
||||
-railTop / 2,
|
||||
railClampable + railBaseLength
|
||||
], %)
|
||||
|> close(%)
|
||||
|> extrude(in2mm(2), %)`
|
||||
return inches * 25.4
|
||||
}
|
||||
|
||||
const railTop = in2mm(.748)
|
||||
const railSide = in2mm(.024)
|
||||
const railBaseWidth = in2mm(.612)
|
||||
const railWideWidth = in2mm(.835)
|
||||
const railBaseLength = in2mm(.200)
|
||||
const railClampable = in2mm(.200)
|
||||
|
||||
const rail = startSketchOn('XZ')
|
||||
|> startProfileAt([
|
||||
-railTop / 2,
|
||||
railClampable + railBaseLength
|
||||
], %)
|
||||
|> lineTo([
|
||||
railTop / 2,
|
||||
railClampable + railBaseLength
|
||||
], %)
|
||||
|> lineTo([
|
||||
railWideWidth / 2,
|
||||
railClampable / 2 + railBaseLength
|
||||
], %, $seg01)
|
||||
|> lineTo([railTop / 2, railBaseLength], %)
|
||||
|> lineTo([railBaseWidth / 2, railBaseLength], %)
|
||||
|> lineTo([railBaseWidth / 2, 0], %)
|
||||
|> lineTo([-railBaseWidth / 2, 0], %)
|
||||
|> lineTo([-railBaseWidth / 2, railBaseLength], %)
|
||||
|> lineTo([-railTop / 2, railBaseLength], %)
|
||||
|> lineTo([
|
||||
-railWideWidth / 2,
|
||||
railClampable / 2 + railBaseLength
|
||||
], %)
|
||||
|> lineTo([
|
||||
-railTop / 2,
|
||||
railClampable + railBaseLength
|
||||
], %)
|
||||
|> close(%)
|
||||
|> extrude(in2mm(2), %)`
|
||||
)
|
||||
})
|
||||
|
||||
const center = { x: 600, y: 250 }
|
||||
const rectangleSize = 20
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Start a sketch
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
@ -1310,33 +1318,27 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Sketch mode should be toleratant to syntax errors', () => {
|
||||
test(
|
||||
test2.describe('Sketch mode should be toleratant to syntax errors', () => {
|
||||
test2(
|
||||
'adding a syntax error, recovers after fixing',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage, context, scene, editor, toolbar }) => {
|
||||
const file = await fs.readFile(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../../',
|
||||
'./src/wasm-lib/tests/executor/inputs/e2e-can-sketch-on-chamfer.kcl'
|
||||
),
|
||||
'utf-8'
|
||||
async ({ app, scene, editor, toolbar }) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'a codemirror error appears in this test only on windows, that causes the test to fail only because of our "no new error" logic, but it can not be replicated locally'
|
||||
)
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
const file = await app.getInputFile('e2e-can-sketch-on-chamfer.kcl')
|
||||
await app.initialise(file)
|
||||
|
||||
const [objClick] = scene.makeMouseHelpers(600, 250)
|
||||
const arrowHeadLocation = { x: 706, y: 129 } as const
|
||||
const arrowHeadLocation = { x: 604, y: 129 } as const
|
||||
const arrowHeadWhite: [number, number, number] = [255, 255, 255]
|
||||
const backgroundGray: [number, number, number] = [28, 28, 28]
|
||||
const verifyArrowHeadColor = async (c: [number, number, number]) =>
|
||||
scene.expectPixelColor(c, arrowHeadLocation, 15)
|
||||
|
||||
await test.step('check chamfer selection changes cursor positon', async () => {
|
||||
await expect(async () => {
|
||||
await expect2(async () => {
|
||||
// sometimes initial click doesn't register
|
||||
await objClick()
|
||||
await editor.expectActiveLinesToBe([
|
||||
@ -1372,56 +1374,48 @@ test.describe('Sketch mode should be toleratant to syntax errors', () => {
|
||||
// this checks sketch segments have been drawn
|
||||
await verifyArrowHeadColor(arrowHeadWhite)
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await app.page.waitForTimeout(100)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe(`Sketching with offset planes`, () => {
|
||||
test(`Can select an offset plane to sketch on`, async ({
|
||||
context,
|
||||
page,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
// We seed the scene with a single offset plane
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`offsetPlane001 = offsetPlane("XY", 10)`
|
||||
)
|
||||
})
|
||||
test2.describe(`Sketching with offset planes`, () => {
|
||||
test2(
|
||||
`Can select an offset plane to sketch on`,
|
||||
async ({ app, scene, toolbar, editor }) => {
|
||||
// We seed the scene with a single offset plane
|
||||
await app.initialise(`offsetPlane001 = offsetPlane("XY", 10)`)
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200)
|
||||
|
||||
const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200)
|
||||
await test2.step(`Start sketching on the offset plane`, async () => {
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
|
||||
await test.step(`Start sketching on the offset plane`, async () => {
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
|
||||
await test.step(`Hovering should highlight code`, async () => {
|
||||
await planeHover()
|
||||
await editor.expectState({
|
||||
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
|
||||
diagnostics: [],
|
||||
highlightedCode: 'offsetPlane("XY", 10)',
|
||||
await test2.step(`Hovering should highlight code`, async () => {
|
||||
await planeHover()
|
||||
await editor.expectState({
|
||||
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
|
||||
diagnostics: [],
|
||||
highlightedCode: 'offsetPlane("XY", 10)',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Clicking should select the plane and enter sketch mode`, async () => {
|
||||
await planeClick()
|
||||
// Have to wait for engine-side animation to finish
|
||||
await page.waitForTimeout(600)
|
||||
await expect(toolbar.lineBtn).toBeEnabled()
|
||||
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
|
||||
await editor.expectState({
|
||||
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
|
||||
diagnostics: [],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await test2.step(
|
||||
`Clicking should select the plane and enter sketch mode`,
|
||||
async () => {
|
||||
await planeClick()
|
||||
// Have to wait for engine-side animation to finish
|
||||
await app.page.waitForTimeout(600)
|
||||
await expect2(toolbar.lineBtn).toBeEnabled()
|
||||
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
|
||||
await editor.expectState({
|
||||
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
|
||||
diagnostics: [],
|
||||
highlightedCode: '',
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -47,12 +47,7 @@ test.beforeEach(async ({ page }) => {
|
||||
|
||||
test.setTimeout(60_000)
|
||||
|
||||
|
||||
// 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(
|
||||
test(
|
||||
'exports of each format should work',
|
||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
||||
async ({ page, context }) => {
|
||||
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
@ -109,21 +109,242 @@ keychain = startSketchOn("XY")
|
||||
|> close(%)
|
||||
|> extrude(thickness, %)
|
||||
|
||||
keychain1 = startSketchOn("XY")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> lineTo([width, 0], %)
|
||||
|> lineTo([width, height], %)
|
||||
|> lineTo([0, height], %)
|
||||
|> close(%)
|
||||
|> extrude(thickness, %)
|
||||
// generated from /home/paultag/Downloads/zma-logomark.svg
|
||||
fn svg = (surface, origin, depth) => {
|
||||
let a0 = surface |> startProfileAt([origin[0] + 45.430427, origin[1] + -14.627736], %)
|
||||
|> bezierCurve({
|
||||
control1: [ 0, 0.764157 ],
|
||||
control2: [ 0, 1.528314 ],
|
||||
to: [ 0, 2.292469 ]
|
||||
}, %)
|
||||
|> 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, %)
|
||||
|
||||
keychain2 = startSketchOn("XY")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> lineTo([width, 0], %)
|
||||
|> lineTo([width, height], %)
|
||||
|> lineTo([0, height], %)
|
||||
|> close(%)
|
||||
|> extrude(thickness, %)
|
||||
let a1 = surface |> startProfileAt([origin[0] + 57.920488, origin[1] + -15.244943], %)
|
||||
|> bezierCurve({
|
||||
control1: [ -2.78904, 0.106635 ],
|
||||
control2: [ -5.052548, -2.969529 ],
|
||||
to: [ -4.055141, -5.598369 ]
|
||||
}, %)
|
||||
|> bezierCurve({
|
||||
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')
|
||||
|> startProfileAt([0, 0], %)
|
||||
@ -133,7 +354,7 @@ box = startSketchOn('XY')
|
||||
|> close(%)
|
||||
|> extrude(10, %)
|
||||
|
||||
sketch001 = startSketchOn(box, revolveAxis)
|
||||
sketch001 = startSketchOn(box, revolveAxis)
|
||||
|> startProfileAt([5, 10], %)
|
||||
|> line([0, -10], %)
|
||||
|> line([2, 0], %)
|
||||
@ -143,12 +364,18 @@ sketch001 = startSketchOn(box, revolveAxis)
|
||||
axis: revolveAxis,
|
||||
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`
|
||||
|
||||
@ -1,16 +1,29 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { commonPoints, getUtils } from './test-utils'
|
||||
import { commonPoints, getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Test network and connection issues', () => {
|
||||
test('simulate network down and network little widget', async ({
|
||||
page,
|
||||
homePage,
|
||||
browserName,
|
||||
}) => {
|
||||
// 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)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
|
||||
@ -49,7 +62,7 @@ test.describe('Test network and connection issues', () => {
|
||||
})
|
||||
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Problem')
|
||||
await expect(networkToggle).toContainText('Offline')
|
||||
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
@ -80,19 +93,26 @@ test.describe('Test network and connection issues', () => {
|
||||
|
||||
test('Engine disconnect & reconnect in sketch mode', async ({
|
||||
page,
|
||||
homePage,
|
||||
browserName,
|
||||
}) => {
|
||||
// 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 u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
@ -112,7 +132,7 @@ test.describe('Test network and connection issues', () => {
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
@ -120,8 +140,8 @@ test.describe('Test network and connection issues', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
@ -136,7 +156,7 @@ test.describe('Test network and connection issues', () => {
|
||||
})
|
||||
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Problem')
|
||||
await expect(networkToggle).toContainText('Offline')
|
||||
|
||||
// Ensure we are not in sketch mode
|
||||
await expect(
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
import {
|
||||
expect,
|
||||
Page,
|
||||
Download,
|
||||
BrowserContext,
|
||||
TestInfo,
|
||||
_electron as electron,
|
||||
Locator,
|
||||
test,
|
||||
} from '@playwright/test'
|
||||
import { test, Page } from './zoo-test'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import fsp from 'fs/promises'
|
||||
import fsSync from 'fs'
|
||||
import path from 'path'
|
||||
import { join } from 'path'
|
||||
import pixelMatch from 'pixelmatch'
|
||||
import { PNG } from 'pngjs'
|
||||
import { Protocol } from 'playwright-core/types/protocol'
|
||||
import type { Models } from '@kittycad/lib'
|
||||
import { COOKIE_NAME } from 'lib/constants'
|
||||
import { APP_NAME, COOKIE_NAME } from 'lib/constants'
|
||||
import { secrets } from './secrets'
|
||||
import {
|
||||
TEST_SETTINGS_KEY,
|
||||
@ -28,10 +30,6 @@ import { isErrorWhitelisted } from './lib/console-error-whitelist'
|
||||
import { isArray } from 'lib/utils'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
|
||||
const toNormalizedCode = (text: string) => {
|
||||
return text.replace(/\s+/g, '')
|
||||
}
|
||||
|
||||
type TestColor = [number, number, number]
|
||||
export const TEST_COLORS = {
|
||||
WHITE: [249, 249, 249] as TestColor,
|
||||
@ -100,16 +98,11 @@ async function removeCurrentCode(page: Page) {
|
||||
}
|
||||
|
||||
export async function sendCustomCmd(page: Page, cmd: EngineCommand) {
|
||||
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-input').fill(JSON.stringify(cmd))
|
||||
await page.getByTestId('custom-cmd-send-button').click()
|
||||
}
|
||||
|
||||
async function clearCommandLogs(page: Page) {
|
||||
await page.getByTestId('custom-cmd-input').fill('')
|
||||
await page.getByTestId('clear-commands').scrollIntoViewIfNeeded()
|
||||
await page.getByTestId('clear-commands').click()
|
||||
}
|
||||
|
||||
@ -157,19 +150,6 @@ export async function closePane(page: Page, testId: string) {
|
||||
|
||||
async function openKclCodePanel(page: Page) {
|
||||
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) {
|
||||
@ -185,9 +165,6 @@ async function closeKclCodePanel(page: Page) {
|
||||
|
||||
async function openDebugPanel(page: Page) {
|
||||
await openPane(page, 'debug-pane-button')
|
||||
|
||||
// The debug pane needs time to load everything.
|
||||
await page.waitForTimeout(3000)
|
||||
}
|
||||
|
||||
export async function closeDebugPanel(page: Page) {
|
||||
@ -435,10 +412,6 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
.boundingBox({ timeout: 5_000 })
|
||||
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
|
||||
codeLocator: page.locator('.cm-content'),
|
||||
crushKclCodeIntoOneLineAndThenMaybeSome: async () => {
|
||||
const code = await page.locator('.cm-content').innerText()
|
||||
return code.replaceAll(' ', '').replaceAll('\n', '')
|
||||
},
|
||||
normalisedEditorCode: async () => {
|
||||
const code = await page.locator('.cm-content').innerText()
|
||||
return normaliseKclNumbers(code)
|
||||
@ -509,18 +482,13 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
)
|
||||
},
|
||||
|
||||
toNormalizedCode(text: string) {
|
||||
return toNormalizedCode(text)
|
||||
toNormalizedCode: (text: string) => {
|
||||
return text.replace(/\s+/g, '')
|
||||
},
|
||||
|
||||
async editorTextMatches(code: string) {
|
||||
editorTextMatches: async (code: string) => {
|
||||
const editor = page.locator(editorSelector)
|
||||
return expect
|
||||
.poll(async () => {
|
||||
const text = await editor.textContent()
|
||||
return toNormalizedCode(text ?? '')
|
||||
})
|
||||
.toContain(toNormalizedCode(code))
|
||||
return expect(editor).toHaveText(code, { useInnerText: true })
|
||||
},
|
||||
|
||||
pasteCodeInEditor: async (code: string) => {
|
||||
@ -546,7 +514,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await page.getByTestId('create-file-button').click()
|
||||
await page.getByTestId('tree-input-field').fill(name)
|
||||
await page.getByTestId('file-rename-field').fill(name)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
},
|
||||
@ -706,34 +674,6 @@ 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
|
||||
})
|
||||
.toBe(1)
|
||||
|
||||
// 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 {
|
||||
modelPath: string
|
||||
imagePath: string
|
||||
@ -746,8 +686,7 @@ export const doExport = async (
|
||||
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
||||
): Promise<Paths> => {
|
||||
if (exportFrom === 'dropdown') {
|
||||
await page.getByTestId('project-sidebar-toggle').click()
|
||||
|
||||
await page.getByRole('button', { name: APP_NAME }).click()
|
||||
const exportMenuButton = page.getByRole('button', {
|
||||
name: 'Export current part',
|
||||
})
|
||||
@ -788,12 +727,25 @@ export const doExport = async (
|
||||
}
|
||||
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()
|
||||
|
||||
// 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') {
|
||||
return {
|
||||
modelPath: '',
|
||||
@ -803,12 +755,15 @@ export const doExport = async (
|
||||
}
|
||||
|
||||
// Handle download
|
||||
const download = await downloadPromise1
|
||||
const downloadLocationer = (extra = '', isImage = false) =>
|
||||
`./e2e/playwright/export-snapshots/${output.type}-${
|
||||
'storage' in output ? output.storage : ''
|
||||
}${extra}.${isImage ? 'png' : output.type}`
|
||||
const downloadLocation = downloadLocationer()
|
||||
|
||||
await download.saveAs(downloadLocation)
|
||||
|
||||
if (output.type === 'step') {
|
||||
// stable timestamps for step files
|
||||
const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
|
||||
@ -817,12 +772,6 @@ export const doExport = async (
|
||||
'1970-01-01T00:00:00.0+00:00'
|
||||
)
|
||||
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 {
|
||||
@ -849,8 +798,6 @@ export async function tearDown(page: Page, testInfo: TestInfo) {
|
||||
// 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
|
||||
await page.waitForTimeout(3000)
|
||||
|
||||
await testInfo.tronApp?.close()
|
||||
}
|
||||
|
||||
// settingsOverrides may need to be augmented to take more generic items,
|
||||
@ -861,19 +808,12 @@ export async function setup(
|
||||
testInfo?: TestInfo
|
||||
) {
|
||||
await context.addInitScript(
|
||||
async ({
|
||||
token,
|
||||
settingsKey,
|
||||
settings,
|
||||
IS_PLAYWRIGHT_KEY,
|
||||
PLAYWRIGHT_TEST_DIR,
|
||||
}) => {
|
||||
async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY }) => {
|
||||
localStorage.clear()
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||
localStorage.setItem('persistCode', ``)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
|
||||
localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR)
|
||||
},
|
||||
{
|
||||
token: secrets.token,
|
||||
@ -890,7 +830,6 @@ export async function setup(
|
||||
} as Partial<SaveSettingsPayload>,
|
||||
}),
|
||||
IS_PLAYWRIGHT_KEY,
|
||||
PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app.projectDirectory,
|
||||
}
|
||||
)
|
||||
|
||||
@ -948,17 +887,13 @@ export async function setupElectron({
|
||||
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
|
||||
: {}),
|
||||
})
|
||||
|
||||
const context = electronApp.context()
|
||||
const page = await electronApp.firstWindow()
|
||||
|
||||
page.TEST_SETTINGS_FILE_KEY = projectDirName
|
||||
|
||||
context.on('console', console.log)
|
||||
page.on('console', console.log)
|
||||
|
||||
if (cleanProjectDir) {
|
||||
const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
|
||||
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
|
||||
const settingsOverrides = TOML.stringify(
|
||||
appSettings
|
||||
? {
|
||||
@ -989,7 +924,7 @@ export async function setupElectron({
|
||||
|
||||
await setup(context, page)
|
||||
|
||||
return { electronApp, page, context, dir: projectDirName }
|
||||
return { electronApp, page, dir: projectDirName }
|
||||
}
|
||||
|
||||
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
||||
@ -1075,7 +1010,7 @@ export async function createProject({
|
||||
}
|
||||
|
||||
export function executorInputPath(fileName: string): string {
|
||||
return path.join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
|
||||
return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
|
||||
}
|
||||
|
||||
export async function doAndWaitForImageDiff(
|
||||
@ -1166,12 +1101,3 @@ 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,14 +1,23 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { getUtils } from './test-utils'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Testing Camera Movement', () => {
|
||||
test('Can move camera reliably', async ({ page, context, homePage }) => {
|
||||
test('Can move camera reliably', async ({ page, context }) => {
|
||||
test.skip(process.platform === 'darwin', 'Can move camera reliably')
|
||||
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 u.openAndClearDebugPanel()
|
||||
await u.closeKclCodePanel()
|
||||
|
||||
@ -174,7 +183,6 @@ test.describe('Testing Camera Movement', () => {
|
||||
|
||||
test('Zoom should be consistent when exiting or entering sketches', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// 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
|
||||
@ -182,9 +190,9 @@ test.describe('Testing Camera Movement', () => {
|
||||
|
||||
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
||||
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 u.openDebugPanel()
|
||||
|
||||
await expect(
|
||||
@ -336,10 +344,7 @@ test.describe('Testing Camera Movement', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test(`Zoom by scroll should not fire while orbiting`, async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test(`Zoom by scroll should not fire while orbiting`, async ({ page }) => {
|
||||
/**
|
||||
* Currently we only allow zooming by scroll when no other camera movement is happening,
|
||||
* set within cameraMouseDragGuards in cameraControls.ts,
|
||||
@ -378,7 +383,7 @@ test.describe('Testing Camera Movement', () => {
|
||||
const expectedOrbitCamZPosition = 64.0
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.closeKclCodePanel()
|
||||
// This test requires the mouse controls to be set to Solidworks
|
||||
await u.openDebugPanel()
|
||||
|
||||
@ -1,60 +1,71 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import {
|
||||
getUtils,
|
||||
TEST_COLORS,
|
||||
pollEditorLinesSelectedLength,
|
||||
} from './test-utils'
|
||||
import { getUtils, setup, tearDown, TEST_COLORS } from './test-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('Can constrain line length', async ({ page, homePage }) => {
|
||||
test('Can constrain line length', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
`
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
// constants and locators
|
||||
const lengthValue = {
|
||||
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 homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click the line of code for line.
|
||||
await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||
// TODO remove this and reinstate `await topHorzSegmentClick()`
|
||||
await page.getByText(`line([0, ${lengthValue.old}], %)`).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// enter sketch again
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
// 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.waitForTimeout(500) // wait for animation
|
||||
await page
|
||||
.getByRole('button', { name: 'dimension Length', exact: true })
|
||||
.click()
|
||||
await page.getByText('Add constraining value').click()
|
||||
await expect(cmdBarKclInput).toHaveText('20')
|
||||
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(
|
||||
`length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
||||
`length001 = ${lengthValue.new}sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
||||
)
|
||||
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
@ -65,45 +76,38 @@ test.describe('Testing constraints', () => {
|
||||
await page.waitForTimeout(500) // wait for animation
|
||||
|
||||
// Exit sketch
|
||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
await page.keyboard.press('Escape', { delay: 500 })
|
||||
return page.getByRole('button', { name: 'Exit Sketch' }).isVisible()
|
||||
})
|
||||
.toBe(true)
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
test(`Remove constraints`, async ({ page, homePage }) => {
|
||||
test(`Remove constraints`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 79
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %, $seg01)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> angledLine([segAng(seg01), yo], %)
|
||||
|> line([41.19, 58.97 + 5], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 120], %)
|
||||
|> xLine(-385.34, %, $seg_what)
|
||||
|> yLine(-170.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %, $seg01)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> angledLine([segAng(seg01), yo], %)
|
||||
|> line([41.19, 58.97 + 5], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 120], %)
|
||||
|> xLine(-385.34, %, $seg_what)
|
||||
|> yLine(-170.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %, $seg01)').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}"]`)
|
||||
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
@ -116,8 +120,8 @@ test.describe('Testing constraints', () => {
|
||||
await page.getByRole('button', { name: 'remove constraints' }).click()
|
||||
|
||||
await page.getByText('line([39.13, 68.63], %)').click()
|
||||
await pollEditorLinesSelectedLength(page, 1)
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent).toHaveLength(1)
|
||||
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
|
||||
@ -135,75 +139,43 @@ test.describe('Testing constraints', () => {
|
||||
},
|
||||
] as const
|
||||
for (const { testName, offset } of cases) {
|
||||
test(`${testName}`, async ({ page, homePage }) => {
|
||||
test(`${testName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %, $seg01)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> angledLine([segAng(seg01), 78.33], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %, $seg01)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> angledLine([segAng(seg01), 78.33], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> 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)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %, $seg01)').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([
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||
])
|
||||
|
||||
await page.mouse.click(line1.x, line1.y)
|
||||
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.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||
await page.keyboard.up('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
@ -231,7 +203,6 @@ test.describe('Testing constraints', () => {
|
||||
`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)`
|
||||
@ -267,37 +238,33 @@ test.describe('Testing constraints', () => {
|
||||
},
|
||||
] as const
|
||||
for (const { testName, value, constraint } of cases) {
|
||||
test(`${constraint} - ${testName}`, async ({ page, homePage }) => {
|
||||
test(`${constraint} - ${testName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %)').click()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
// Wait for overlays to populate
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
const [line1, line3] = await Promise.all([
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||
@ -377,37 +344,33 @@ test.describe('Testing constraints', () => {
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, constraint } of cases) {
|
||||
test(`${constraint} - ${testName}`, async ({ page, homePage }) => {
|
||||
test(`${constraint} - ${testName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %)').click()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
// Wait for overlays to populate
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
const [line3] = await Promise.all([
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||
])
|
||||
@ -418,11 +381,9 @@ test.describe('Testing constraints', () => {
|
||||
await page.mouse.click(900, 250)
|
||||
}
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page.waitForTimeout(100)
|
||||
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||
await page.keyboard.up('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
@ -490,37 +451,33 @@ test.describe('Testing constraints', () => {
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, axisSelect } of cases) {
|
||||
test(`${testName}`, async ({ page, homePage }) => {
|
||||
test(`${testName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %)').click()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
// Wait for overlays to populate
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
const [line1, line3] = await Promise.all([
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||
@ -576,7 +533,7 @@ test.describe('Testing constraints', () => {
|
||||
})
|
||||
}
|
||||
})
|
||||
test.describe('Test Angle/Length constraint single selection', () => {
|
||||
test.describe('Test Angle constraint single selection', () => {
|
||||
const cases = [
|
||||
{
|
||||
testName: 'Angle - Add variable',
|
||||
@ -590,51 +547,35 @@ test.describe('Testing constraints', () => {
|
||||
constraint: 'angle',
|
||||
value: '83, 78.33',
|
||||
},
|
||||
{
|
||||
testName: 'Length - Add variable',
|
||||
addVariable: true,
|
||||
constraint: 'length',
|
||||
value: '83, length001',
|
||||
},
|
||||
{
|
||||
testName: 'Length - No variable',
|
||||
addVariable: false,
|
||||
constraint: 'length',
|
||||
value: '83, 78.33',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, constraint } of cases) {
|
||||
test(`${testName}`, async ({ page, homePage }) => {
|
||||
test(`${testName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %)').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}"]`
|
||||
)
|
||||
@ -664,6 +605,90 @@ test.describe('Testing constraints', () => {
|
||||
})
|
||||
}
|
||||
})
|
||||
test.describe('Test Length constraint single selection', () => {
|
||||
const cases = [
|
||||
{
|
||||
testName: 'Length - Add variable',
|
||||
addVariable: true,
|
||||
constraint: 'length',
|
||||
value: '83, length001',
|
||||
},
|
||||
{
|
||||
testName: 'Length - No variable',
|
||||
addVariable: false,
|
||||
constraint: 'length',
|
||||
value: '83, 78.33',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, constraint } of cases) {
|
||||
test(`${testName}`, async ({ page }) => {
|
||||
// constants and locators
|
||||
const cmdBarKclInput = page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.getByRole('textbox')
|
||||
const cmdBarKclVariableNameInput =
|
||||
page.getByPlaceholder('Variable name')
|
||||
const cmdBarSubmitButton = page.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %)').click()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
const line3 = await u.getSegmentBodyCoords(
|
||||
`[data-overlay-index="${2}"]`
|
||||
)
|
||||
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
await page.getByTestId('dropdown-constraint-' + constraint).click()
|
||||
|
||||
if (!addVariable) {
|
||||
await test.step(`Clear the variable input`, async () => {
|
||||
await cmdBarKclVariableNameInput.clear()
|
||||
await cmdBarKclVariableNameInput.press('Backspace')
|
||||
})
|
||||
}
|
||||
await expect(cmdBarKclInput).toHaveText('78.33')
|
||||
await cmdBarSubmitButton.click()
|
||||
|
||||
const changedCode = `|> angledLine([${value}], %)`
|
||||
await expect(page.locator('.cm-content')).toContainText(changedCode)
|
||||
// checking active assures the cursor is where it should be
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
})
|
||||
test.describe('Many segments - no modal constraints', () => {
|
||||
const cases = [
|
||||
{
|
||||
@ -684,37 +709,33 @@ test.describe('Testing constraints', () => {
|
||||
},
|
||||
] as const
|
||||
for (const { codeAfter, constraintName } of cases) {
|
||||
test(`${constraintName}`, async ({ page, homePage }) => {
|
||||
test(`${constraintName}`, async ({ page }) => {
|
||||
await page.addInitScript(async (customCode) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %)').click()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
// Wait for overlays to populate
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
const line1 = await u.getSegmentBodyCoords(
|
||||
`[data-overlay-index="${0}"]`
|
||||
)
|
||||
@ -733,8 +754,8 @@ test.describe('Testing constraints', () => {
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
// check actives lines
|
||||
await pollEditorLinesSelectedLength(page, codeAfter.length)
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent).toHaveLength(codeAfter.length)
|
||||
|
||||
const constraintMenuButton = page.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
@ -785,36 +806,32 @@ test.describe('Testing constraints', () => {
|
||||
},
|
||||
] as const
|
||||
for (const { codeAfter, constraintName } of cases) {
|
||||
test(`${constraintName}`, async ({ page, homePage }) => {
|
||||
test(`${constraintName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %)').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 line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`)
|
||||
|
||||
@ -841,8 +858,8 @@ test.describe('Testing constraints', () => {
|
||||
// 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)
|
||||
// check actives lines
|
||||
await pollEditorLinesSelectedLength(page, 2)
|
||||
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
|
||||
await expect(activeLinesContent[0]).toHaveText(
|
||||
@ -866,47 +883,40 @@ test.describe('Testing constraints', () => {
|
||||
},
|
||||
] as const
|
||||
for (const { codeAfter, constraintName, axisClick } of cases) {
|
||||
test(`${constraintName}`, async ({ page, homePage }) => {
|
||||
test(`${constraintName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %)').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}"]`)
|
||||
|
||||
// select segment and axis by holding down shift
|
||||
await page.mouse.click(line3.x - 3, line3.y + 20)
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(axisClick.x, axisClick.y)
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.up('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
const constraintMenuButton = page.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
@ -928,23 +938,30 @@ test.describe('Testing constraints', () => {
|
||||
|
||||
test('Horizontally constrained line remains selected after applying constraint', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test.setTimeout(70_000)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-1.05, -1.07], %)
|
||||
|> line([3.79, 2.68], %, $seg01)
|
||||
|> line([3.13, -2.4], %)`
|
||||
|> startProfileAt([-1.05, -1.07], %)
|
||||
|> line([3.79, 2.68], %, $seg01)
|
||||
|> line([3.13, -2.4], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
// 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)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([3.79, 2.68], %, $seg01)').click()
|
||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
||||
@ -952,9 +969,6 @@ test.describe('Testing constraints', () => {
|
||||
)
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
// Wait for overlays to populate
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
const lineBefore = await u.getSegmentBodyCoords(
|
||||
`[data-overlay-index="1"]`,
|
||||
@ -975,17 +989,11 @@ test.describe('Testing constraints', () => {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
await page.waitForTimeout(500)
|
||||
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()
|
||||
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
|
||||
await expect(
|
||||
await page.locator('[data-overlay-index="1"]')
|
||||
@ -995,17 +1003,11 @@ test.describe('Testing constraints', () => {
|
||||
`[data-overlay-index="1"]`,
|
||||
0
|
||||
)
|
||||
expect(
|
||||
await u.getGreatestPixDiff(lineAfter, TEST_COLORS.BLUE)
|
||||
).toBeLessThan(3)
|
||||
|
||||
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.waitForTimeout(300)
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
@ -1016,10 +1018,9 @@ test.describe('Testing constraints', () => {
|
||||
// await page.getByRole('button', { name: 'length', exact: true }).click()
|
||||
await page.getByTestId('dropdown-constraint-length').click()
|
||||
|
||||
await page.getByLabel('length Value').fill('10')
|
||||
await page.getByRole('button', { name: 'Add constraining value' }).click()
|
||||
await cmdBarKclInput.fill('10')
|
||||
await cmdBarSubmitButton.click()
|
||||
|
||||
await pollEditorLinesSelectedLength(page, 1)
|
||||
activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
||||
|
||||
|
||||
@ -1,9 +1,18 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { getUtils } from './test-utils'
|
||||
import { _test, _expect } from './playwright-deprecated'
|
||||
import { test } from './fixtures/fixtureSetup'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { TEST_CODE_GIZMO } from './storageStates'
|
||||
|
||||
test.describe('Testing Gizmo', () => {
|
||||
_test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
_test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
_test.describe('Testing Gizmo', () => {
|
||||
const cases = [
|
||||
{
|
||||
testDescription: 'top view',
|
||||
@ -48,17 +57,14 @@ test.describe('Testing Gizmo', () => {
|
||||
expectedCameraTarget,
|
||||
testDescription,
|
||||
} of cases) {
|
||||
test(`check ${testDescription}`, async ({ page, homePage }) => {
|
||||
_test(`check ${testDescription}`, async ({ page, browserName }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript((TEST_CODE_GIZMO) => {
|
||||
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
||||
}, TEST_CODE_GIZMO)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.waitForTimeout(100)
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -111,30 +117,30 @@ test.describe('Testing Gizmo', () => {
|
||||
|
||||
await Promise.all([
|
||||
// position
|
||||
expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||
expectedCameraPosition.x.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||
expectedCameraPosition.y.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||
expectedCameraPosition.z.toString()
|
||||
),
|
||||
// target
|
||||
expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||
expectedCameraTarget.x.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||
expectedCameraTarget.y.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||
expectedCameraTarget.z.toString()
|
||||
),
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
test('Context menu and popover menu', async ({ page, homePage }) => {
|
||||
_test('Context menu and popover menu', async ({ page }) => {
|
||||
const testCase = {
|
||||
testDescription: 'Right view',
|
||||
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
|
||||
@ -146,9 +152,9 @@ test.describe('Testing Gizmo', () => {
|
||||
await page.addInitScript((TEST_CODE_GIZMO) => {
|
||||
localStorage.setItem('persistCode', TEST_CODE_GIZMO)
|
||||
}, TEST_CODE_GIZMO)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.waitForTimeout(100)
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -190,7 +196,7 @@ test.describe('Testing Gizmo', () => {
|
||||
const buttonToTest = page.getByRole('button', {
|
||||
name: testCase.testDescription,
|
||||
})
|
||||
await expect(buttonToTest).toBeVisible()
|
||||
await _expect(buttonToTest).toBeVisible()
|
||||
await buttonToTest.click()
|
||||
|
||||
// Now assert we've moved to the correct view
|
||||
@ -209,23 +215,23 @@ test.describe('Testing Gizmo', () => {
|
||||
|
||||
await Promise.all([
|
||||
// position
|
||||
expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||
testCase.expectedCameraPosition.x.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||
testCase.expectedCameraPosition.y.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||
testCase.expectedCameraPosition.z.toString()
|
||||
),
|
||||
// target
|
||||
expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||
testCase.expectedCameraTarget.x.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||
testCase.expectedCameraTarget.y.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||
_expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||
testCase.expectedCameraTarget.z.toString()
|
||||
),
|
||||
])
|
||||
@ -236,59 +242,32 @@ test.describe('Testing Gizmo', () => {
|
||||
const gizmoPopoverButton = page.getByRole('button', {
|
||||
name: 'view settings',
|
||||
})
|
||||
await expect(gizmoPopoverButton).toBeVisible()
|
||||
await _expect(gizmoPopoverButton).toBeVisible()
|
||||
await gizmoPopoverButton.click()
|
||||
await expect(buttonToTest).toBeVisible()
|
||||
await _expect(buttonToTest).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe(`Testing gizmo, fixture-based`, () => {
|
||||
test('Center on selection from menu', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
app,
|
||||
cmdBar,
|
||||
editor,
|
||||
toolbar,
|
||||
scene,
|
||||
}) => {
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'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()
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'Fails on windows in CI, can not be replicated locally on windows.'
|
||||
)
|
||||
|
||||
await test.step(`Setup`, async () => {
|
||||
const file = await app.getInputFile('test-circle-extrude.kcl')
|
||||
await app.initialise(file)
|
||||
await scene.expectState({
|
||||
camera: {
|
||||
position: [11912.6, -39586.98, 21391.21],
|
||||
target: [11912.6, -635, 3317.49],
|
||||
position: [4982.21, -23865.37, 13810.64],
|
||||
target: [4982.21, 0, 2737.1],
|
||||
},
|
||||
})
|
||||
})
|
||||
@ -296,7 +275,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
||||
|
||||
await test.step(`Select an edge of this circle`, async () => {
|
||||
const circleSnippet =
|
||||
'circle({ center: [818.33, 168.1], radius: 182.8 }, %)'
|
||||
'circle({ center = [318.33, 168.1], radius = 182.8 }, %)'
|
||||
await moveToCircle()
|
||||
await clickCircle()
|
||||
await editor.expectState({
|
||||
@ -313,8 +292,8 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
||||
await test.step(`Verify the camera moved`, async () => {
|
||||
await scene.expectState({
|
||||
camera: {
|
||||
position: [20785.58, -40221.98, 22343.46],
|
||||
target: [20785.58, -1270, 4269.74],
|
||||
position: [0, -23865.37, 11073.53],
|
||||
target: [0, 0, 0],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,8 +1,18 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { getUtils } from './test-utils'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } 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.fixme('via command palette and toggle', async ({ page, homePage }) => {
|
||||
test('via command palette and toggle', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Locators and constants
|
||||
@ -10,7 +20,7 @@ test.describe('Test toggling perspective', () => {
|
||||
const screenHeight = 500
|
||||
const checkedScreenLocation = {
|
||||
x: screenWidth * 0.71,
|
||||
y: screenHeight * 0.2,
|
||||
y: screenHeight * 0.4,
|
||||
}
|
||||
const backgroundColor: [number, number, number] = [29, 29, 29]
|
||||
const xzPlaneColor: [number, number, number] = [82, 55, 96]
|
||||
@ -30,8 +40,8 @@ test.describe('Test toggling perspective', () => {
|
||||
})
|
||||
|
||||
await test.step('Setup', async () => {
|
||||
await page.setBodyDimensions({ width: screenWidth, height: screenHeight })
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: screenWidth, height: screenHeight })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.closeKclCodePanel()
|
||||
await expect
|
||||
.poll(async () => locationToHaveColor(backgroundColor), {
|
||||
@ -42,17 +52,11 @@ test.describe('Test toggling perspective', () => {
|
||||
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 commandPaletteButton.click()
|
||||
await page.waitForTimeout(1000)
|
||||
await commandOption.click()
|
||||
await page.waitForTimeout(1000)
|
||||
await orthoOption.click()
|
||||
await expect(commandToast).toBeVisible()
|
||||
await expect(commandToast).not.toBeVisible()
|
||||
await expect
|
||||
.poll(async () => locationToHaveColor(xzPlaneColor), {
|
||||
timeout: 5000,
|
||||
@ -63,9 +67,27 @@ test.describe('Test toggling perspective', () => {
|
||||
})
|
||||
|
||||
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.waitForTimeout(1000)
|
||||
await u.closeKclCodePanel()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect
|
||||
.poll(async () => locationToHaveColor(xzPlaneColor), {
|
||||
timeout: 5000,
|
||||
|
||||
@ -1,30 +1,35 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { getUtils } from './test-utils'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
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', () => {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
test('Web: should overwrite current code, cannot create new file', async ({
|
||||
editor,
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await context.addInitScript((code) => {
|
||||
await page.addInitScript((code) => {
|
||||
window.localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
})
|
||||
|
||||
// Locators and constants
|
||||
@ -49,13 +54,13 @@ test.describe('Testing in-app sample loading', () => {
|
||||
})
|
||||
const warningText = page.getByText('Overwrite current file and units?')
|
||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
const unitsToast = (unit: UnitLength_type) =>
|
||||
page.getByText(`Set default unit to "${unit}" for this project`)
|
||||
|
||||
await test.step(`Precondition: check the initial code`, async () => {
|
||||
await u.openKclCodePanel()
|
||||
await editor.scrollToText(bracket.split('\n')[0])
|
||||
await editor.expectEditor.toContain(bracket.split('\n')[0])
|
||||
await expect(codeLocator).toContainText(bracket.split('\n')[0])
|
||||
})
|
||||
|
||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||
@ -68,7 +73,7 @@ test.describe('Testing in-app sample loading', () => {
|
||||
await expect(warningText).toBeVisible()
|
||||
await confirmButton.click()
|
||||
|
||||
await editor.expectEditor.toContain('// ' + newSample.title)
|
||||
await expect(codeLocator).toContainText('// ' + newSample.title)
|
||||
await expect(unitsToast('in')).toBeVisible()
|
||||
})
|
||||
})
|
||||
@ -81,13 +86,16 @@ test.describe('Testing in-app sample loading', () => {
|
||||
test(
|
||||
'Desktop: should create new file by default, optionally overwrite',
|
||||
{ tag: '@electron' },
|
||||
async ({ editor, context, page }, testInfo) => {
|
||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), bracket, {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), bracket, {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -126,19 +134,19 @@ test.describe('Testing in-app sample loading', () => {
|
||||
page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name }),
|
||||
})
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
const unitsToast = (unit: UnitLength_type) =>
|
||||
page.getByText(`Set default unit to "${unit}" for this project`)
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
await test.step(`Precondition: check the initial code`, async () => {
|
||||
await u.openKclCodePanel()
|
||||
await editor.scrollToText(bracket.split('\n')[0])
|
||||
await editor.expectEditor.toContain(bracket.split('\n')[0])
|
||||
await expect(codeLocator).toContainText(bracket.split('\n')[0])
|
||||
await u.openFilePanel()
|
||||
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
@ -155,7 +163,7 @@ test.describe('Testing in-app sample loading', () => {
|
||||
})
|
||||
|
||||
await test.step(`Ensure we made and opened a new file`, async () => {
|
||||
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
||||
await expect(codeLocator).toContainText('// ' + sampleOne.title)
|
||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||
await expect(unitsToast('in')).toBeVisible()
|
||||
@ -174,7 +182,7 @@ test.describe('Testing in-app sample loading', () => {
|
||||
})
|
||||
|
||||
await test.step(`Ensure we overwrote the current file without navigating`, async () => {
|
||||
await editor.expectEditor.toContain('// ' + sampleTwo.title)
|
||||
await expect(codeLocator).toContainText('// ' + sampleTwo.title)
|
||||
await test.step(`Check actual file contents`, async () => {
|
||||
await expect
|
||||
.poll(async () => {
|
||||
@ -190,6 +198,8 @@ test.describe('Testing in-app sample loading', () => {
|
||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||
await expect(unitsToast('mm')).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
|
||||
import { deg, getUtils, wiggleMove } from './test-utils'
|
||||
import { deg, getUtils, setup, tearDown, wiggleMove } from './test-utils'
|
||||
import { LineInputsType } from 'lang/std/sketchcombos'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { EditorFixture } from './fixtures/editorFixture'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Testing segment overlays', () => {
|
||||
test.describe('Hover over a segment should show its overlay, hovering over the input overlays should show its popover, clicking the input overlay should constrain/unconstrain it:\nfor the following segments', () => {
|
||||
@ -17,7 +24,7 @@ test.describe('Testing segment overlays', () => {
|
||||
* @param {number} options.steps - The number of steps to perform
|
||||
*/
|
||||
const _clickConstrained =
|
||||
(page: Page, editor: EditorFixture) =>
|
||||
(page: Page) =>
|
||||
async ({
|
||||
hoverPos,
|
||||
constraintType,
|
||||
@ -51,11 +58,10 @@ test.describe('Testing segment overlays', () => {
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
await page.mouse.move(x, y)
|
||||
|
||||
await editor.expectEditor.toContain(expectBeforeUnconstrained, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
expectBeforeUnconstrained
|
||||
)
|
||||
const constrainedLocator = page.locator(
|
||||
`[data-constraint-type="${constraintType}"][data-is-constrained="true"]`
|
||||
)
|
||||
@ -65,9 +71,9 @@ test.describe('Testing segment overlays', () => {
|
||||
await page.getByTestId('constraint-symbol-popover').count()
|
||||
).toBeGreaterThan(0)
|
||||
await constrainedLocator.click()
|
||||
await editor.expectEditor.toContain(expectAfterUnconstrained, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
expectAfterUnconstrained
|
||||
)
|
||||
|
||||
await page.mouse.move(0, 0)
|
||||
await page.waitForTimeout(1000)
|
||||
@ -75,7 +81,6 @@ test.describe('Testing segment overlays', () => {
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
await page.mouse.move(x, y)
|
||||
|
||||
const unconstrainedLocator = page.locator(
|
||||
`[data-constraint-type="${constraintType}"][data-is-constrained="false"]`
|
||||
@ -86,10 +91,15 @@ test.describe('Testing segment overlays', () => {
|
||||
await page.getByTestId('constraint-symbol-popover').count()
|
||||
).toBeGreaterThan(0)
|
||||
await unconstrainedLocator.click()
|
||||
await page.getByText('Add variable').click()
|
||||
await editor.expectEditor.toContain(expectFinal, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(
|
||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||
).toBeFocused()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
.click()
|
||||
await expect(page.locator('.cm-content')).toContainText(expectFinal)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,7 +112,7 @@ test.describe('Testing segment overlays', () => {
|
||||
* @param {number} options.steps - The number of steps to perform
|
||||
*/
|
||||
const _clickUnconstrained =
|
||||
(page: Page, editor: EditorFixture) =>
|
||||
(page: Page) =>
|
||||
async ({
|
||||
hoverPos,
|
||||
constraintType,
|
||||
@ -134,12 +144,11 @@ test.describe('Testing segment overlays', () => {
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
await page.mouse.move(x, y)
|
||||
|
||||
await expect(page.getByText('Added variable')).not.toBeVisible()
|
||||
await editor.expectEditor.toContain(expectBeforeUnconstrained, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
expectBeforeUnconstrained
|
||||
)
|
||||
const unconstrainedLocator = page.locator(
|
||||
`[data-constraint-type="${constraintType}"][data-is-constrained="false"]`
|
||||
)
|
||||
@ -149,10 +158,17 @@ test.describe('Testing segment overlays', () => {
|
||||
await page.getByTestId('constraint-symbol-popover').count()
|
||||
).toBeGreaterThan(0)
|
||||
await unconstrainedLocator.click()
|
||||
await page.getByText('Add variable').click()
|
||||
await editor.expectEditor.toContain(expectAfterUnconstrained, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(
|
||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||
).toBeFocused()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
.click()
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
expectAfterUnconstrained
|
||||
)
|
||||
await expect(page.getByText('Added variable')).not.toBeVisible()
|
||||
|
||||
await page.mouse.move(0, 0)
|
||||
@ -161,7 +177,6 @@ test.describe('Testing segment overlays', () => {
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
await page.mouse.move(x, y)
|
||||
|
||||
const constrainedLocator = page.locator(
|
||||
`[data-constraint-type="${constraintType}"][data-is-constrained="true"]`
|
||||
@ -172,45 +187,41 @@ test.describe('Testing segment overlays', () => {
|
||||
await page.getByTestId('constraint-symbol-popover').count()
|
||||
).toBeGreaterThan(0)
|
||||
await constrainedLocator.click()
|
||||
await editor.expectEditor.toContain(expectFinal, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toContainText(expectFinal)
|
||||
}
|
||||
test.setTimeout(120000)
|
||||
test('for segments [line, angledLine, lineTo, xLineTo]', async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([5 + 0, 20 + 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([5 + 33, 20 + 11.5 + 0], %)
|
||||
|> xLineTo(5 + 9 - 5, %)
|
||||
|> yLineTo(20 + -10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 5 + 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 20 + 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([5 + 3.14 + 13, 20 + 3.14], %)
|
||||
`
|
||||
|> startProfileAt([5 + 0, 20 + 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([5 + 33, 20 + 11.5 + 0], %)
|
||||
|> xLineTo(5 + 9 - 5, %)
|
||||
|> yLineTo(20 + -10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 5 + 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 20 + 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([5 + 3.14 + 13, 20 + 3.14], %)
|
||||
`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -224,8 +235,8 @@ test.describe('Testing segment overlays', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(13)
|
||||
|
||||
const clickUnconstrained = _clickUnconstrained(page, editor)
|
||||
const clickConstrained = _clickConstrained(page, editor)
|
||||
const clickUnconstrained = _clickUnconstrained(page)
|
||||
const clickConstrained = _clickConstrained(page)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
@ -343,38 +354,34 @@ test.describe('Testing segment overlays', () => {
|
||||
locator: '[data-overlay-toolbar-index="3"]',
|
||||
})
|
||||
})
|
||||
test('for segments [yLineTo, xLine]', async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
test('for segments [yLineTo, xLine]', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yRel001 = -14
|
||||
xRel001 = 0.5
|
||||
angle001 = 3
|
||||
len001 = 32
|
||||
yAbs001 = 11.5
|
||||
xAbs001 = 33
|
||||
xAbs002 = 4
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, yRel001], %)
|
||||
|> angledLine({ angle = angle001, length = len001 }, %)
|
||||
|> lineTo([33, yAbs001], %)
|
||||
|> xLineTo(xAbs002, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
`
|
||||
xRel001 = 0.5
|
||||
angle001 = 3
|
||||
len001 = 32
|
||||
yAbs001 = 11.5
|
||||
xAbs001 = 33
|
||||
xAbs002 = 4
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, yRel001], %)
|
||||
|> angledLine({ angle = angle001, length = len001 }, %)
|
||||
|> lineTo([33, yAbs001], %)
|
||||
|> xLineTo(xAbs002, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -388,7 +395,7 @@ test.describe('Testing segment overlays', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(8)
|
||||
|
||||
const clickUnconstrained = _clickUnconstrained(page, editor)
|
||||
const clickUnconstrained = _clickUnconstrained(page)
|
||||
|
||||
await page.mouse.move(700, 250)
|
||||
await page.waitForTimeout(100)
|
||||
@ -424,39 +431,37 @@ test.describe('Testing segment overlays', () => {
|
||||
})
|
||||
test('for segments [yLine, angledLineOfXLength, angledLineOfYLength]', async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([33, 11.5 + 0], %)
|
||||
|> xLineTo(9 - 5, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, 3.14], %)
|
||||
`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([33, 11.5 + 0], %)
|
||||
|> xLineTo(9 - 5, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, 3.14], %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -471,8 +476,8 @@ test.describe('Testing segment overlays', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(13)
|
||||
|
||||
const clickUnconstrained = _clickUnconstrained(page, editor)
|
||||
const clickConstrained = _clickConstrained(page, editor)
|
||||
const clickUnconstrained = _clickUnconstrained(page)
|
||||
const clickConstrained = _clickConstrained(page)
|
||||
|
||||
let ang = 0
|
||||
|
||||
@ -555,39 +560,37 @@ test.describe('Testing segment overlays', () => {
|
||||
})
|
||||
test('for segments [angledLineToX, angledLineToY, angledLineThatIntersects]', async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([33, 11.5 + 0], %)
|
||||
|> xLineTo(9 - 5, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, 1.14], %)
|
||||
`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([33, 11.5 + 0], %)
|
||||
|> xLineTo(9 - 5, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, 1.14], %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -601,8 +604,8 @@ test.describe('Testing segment overlays', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(13)
|
||||
|
||||
const clickUnconstrained = _clickUnconstrained(page, editor)
|
||||
const clickConstrained = _clickConstrained(page, editor)
|
||||
const clickUnconstrained = _clickUnconstrained(page)
|
||||
const clickConstrained = _clickConstrained(page)
|
||||
|
||||
let ang = 0
|
||||
|
||||
@ -671,20 +674,20 @@ test.describe('Testing segment overlays', () => {
|
||||
},
|
||||
constraintType: 'angle',
|
||||
expectBeforeUnconstrained: `angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)`,
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)`,
|
||||
expectAfterUnconstrained: `angledLineThatIntersects({
|
||||
angle = angle003,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)`,
|
||||
angle = angle003,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)`,
|
||||
expectFinal: `angledLineThatIntersects({
|
||||
angle = -176,
|
||||
offset = 9,
|
||||
intersectTag = a
|
||||
}, %)`,
|
||||
angle = -176,
|
||||
offset = 9,
|
||||
intersectTag = a
|
||||
}, %)`,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="11"]',
|
||||
})
|
||||
@ -696,59 +699,55 @@ test.describe('Testing segment overlays', () => {
|
||||
},
|
||||
constraintType: 'intersectionOffset',
|
||||
expectBeforeUnconstrained: `angledLineThatIntersects({
|
||||
angle = -176,
|
||||
offset = 9,
|
||||
intersectTag = a
|
||||
}, %)`,
|
||||
angle = -176,
|
||||
offset = 9,
|
||||
intersectTag = a
|
||||
}, %)`,
|
||||
expectAfterUnconstrained: `angledLineThatIntersects({
|
||||
angle = -176,
|
||||
offset = perpDist001,
|
||||
intersectTag = a
|
||||
}, %)`,
|
||||
angle = -176,
|
||||
offset = perpDist001,
|
||||
intersectTag = a
|
||||
}, %)`,
|
||||
expectFinal: `angledLineThatIntersects({
|
||||
angle = -176,
|
||||
offset = 9,
|
||||
intersectTag = a
|
||||
}, %)`,
|
||||
angle = -176,
|
||||
offset = 9,
|
||||
intersectTag = a
|
||||
}, %)`,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="11"]',
|
||||
})
|
||||
})
|
||||
test('for segment [tangentialArcTo]', async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
test('for segment [tangentialArcTo]', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([33, 11.5 + 0], %)
|
||||
|> xLineTo(9 - 5, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, -3.14], %)
|
||||
`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([33, 11.5 + 0], %)
|
||||
|> xLineTo(9 - 5, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, -3.14], %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -762,8 +761,8 @@ test.describe('Testing segment overlays', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(13)
|
||||
|
||||
const clickUnconstrained = _clickUnconstrained(page, editor)
|
||||
const clickConstrained = _clickConstrained(page, editor)
|
||||
const clickUnconstrained = _clickUnconstrained(page)
|
||||
const clickConstrained = _clickConstrained(page)
|
||||
|
||||
const tangentialArcTo = await u.getBoundingBox(
|
||||
'[data-overlay-index="12"]'
|
||||
@ -792,20 +791,20 @@ test.describe('Testing segment overlays', () => {
|
||||
locator: '[data-overlay-toolbar-index="12"]',
|
||||
})
|
||||
})
|
||||
test('for segment [circle]', async ({ page, editor, homePage }) => {
|
||||
test('for segment [circle]', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> circle({ center = [1 + 0, 0], radius = 8 }, %)
|
||||
`
|
||||
|> circle({ center = [1 + 0, 0], radius = 8 }, %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -821,8 +820,8 @@ test.describe('Testing segment overlays', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(1)
|
||||
|
||||
const clickUnconstrained = _clickUnconstrained(page, editor)
|
||||
const clickConstrained = _clickConstrained(page, editor)
|
||||
const clickUnconstrained = _clickUnconstrained(page)
|
||||
const clickConstrained = _clickConstrained(page)
|
||||
|
||||
const hoverPos = { x: 789, y: 114 } as const
|
||||
let ang = await u.getAngle('[data-overlay-index="0"]')
|
||||
@ -848,8 +847,8 @@ test.describe('Testing segment overlays', () => {
|
||||
expectAfterUnconstrained:
|
||||
'circle({ center = [xAbs001, yAbs001], radius = 8 }, %)',
|
||||
expectFinal: 'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
||||
ang: ang + 180,
|
||||
steps: 30,
|
||||
ang: ang + 105,
|
||||
steps: 10,
|
||||
locator: '[data-overlay-toolbar-index="0"]',
|
||||
})
|
||||
console.log('circle radius')
|
||||
@ -869,7 +868,7 @@ test.describe('Testing segment overlays', () => {
|
||||
})
|
||||
test.describe('Testing deleting a segment', () => {
|
||||
const _deleteSegmentSequence =
|
||||
(page: Page, editor: EditorFixture) =>
|
||||
(page: Page) =>
|
||||
async ({
|
||||
hoverPos,
|
||||
codeToBeDeleted,
|
||||
@ -895,51 +894,47 @@ test.describe('Testing segment overlays', () => {
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
await page.mouse.move(x, y)
|
||||
|
||||
await editor.expectEditor.toContain(codeToBeDeleted, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toContainText(codeToBeDeleted)
|
||||
|
||||
await page.locator(`[data-stdlib-fn-name="${stdLibFnName}"]`).click()
|
||||
await page.getByText('Delete Segment').click()
|
||||
|
||||
await editor.expectEditor.not.toContain(codeToBeDeleted, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toContainText(
|
||||
codeToBeDeleted
|
||||
)
|
||||
}
|
||||
test('all segment types', async ({ page, editor, homePage }) => {
|
||||
test('all segment types', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([33, 11.5 + 0], %)
|
||||
|> xLineTo(9 - 5, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, 1.14], %)
|
||||
`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0.5, -14 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> lineTo([33, 11.5 + 0], %)
|
||||
|> xLineTo(9 - 5, %)
|
||||
|> yLineTo(-10.77, %, $a)
|
||||
|> xLine(26.04, %)
|
||||
|> yLine(21.14 + 0, %)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, 1.14], %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -952,7 +947,7 @@ test.describe('Testing segment overlays', () => {
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(13)
|
||||
const deleteSegmentSequence = _deleteSegmentSequence(page, editor)
|
||||
const deleteSegmentSequence = _deleteSegmentSequence(page)
|
||||
|
||||
let segmentToDelete
|
||||
|
||||
@ -977,7 +972,7 @@ test.describe('Testing segment overlays', () => {
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)`,
|
||||
}, %)`,
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
ang: ang + 180,
|
||||
steps: 7,
|
||||
@ -1085,19 +1080,16 @@ test.describe('Testing segment overlays', () => {
|
||||
5,
|
||||
'[data-overlay-toolbar-index="2"]'
|
||||
)
|
||||
await page.mouse.move(hoverPos.x, hoverPos.y)
|
||||
|
||||
const codeToBeDeleted = 'lineTo([33, 11.5 + 0], %)'
|
||||
await editor.expectEditor.toContain(codeToBeDeleted, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toContainText(codeToBeDeleted)
|
||||
|
||||
await page.getByTestId('overlay-menu').click()
|
||||
await page.getByText('Delete Segment').click()
|
||||
|
||||
await editor.expectEditor.not.toContain(codeToBeDeleted, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toContainText(
|
||||
codeToBeDeleted
|
||||
)
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(1)
|
||||
ang = await u.getAngle(`[data-overlay-index="${1}"]`)
|
||||
@ -1143,17 +1135,17 @@ test.describe('Testing segment overlays', () => {
|
||||
const isObj = lineOfInterest.includes('{ angle = 3,')
|
||||
test(`${lineOfInterest.split('(')[0]}${isObj ? '-[obj-input]' : ''}${
|
||||
doesHaveTagOutsideSketch ? '-[tagOutsideSketch]' : ''
|
||||
}`, async ({ page, editor, homePage }) => {
|
||||
}`, async ({ page }) => {
|
||||
await page.addInitScript(
|
||||
async ({ lineToBeDeleted, extraLine }) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([5, 6], %)
|
||||
|> ${lineToBeDeleted}
|
||||
|> line([-10, -15], %)
|
||||
|> angledLine([-176, segLen(seg01)], %)
|
||||
${extraLine ? 'myVar = segLen(seg01)' : ''}`
|
||||
|> startProfileAt([5, 6], %)
|
||||
|> ${lineToBeDeleted}
|
||||
|> line([-10, -15], %)
|
||||
|> angledLine([-176, segLen(seg01)], %)
|
||||
${extraLine ? 'myVar = segLen(seg01)' : ''}`
|
||||
)
|
||||
},
|
||||
{
|
||||
@ -1164,7 +1156,7 @@ test.describe('Testing segment overlays', () => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
await page.getByText(lineOfInterest).click()
|
||||
@ -1192,9 +1184,9 @@ test.describe('Testing segment overlays', () => {
|
||||
await page.mouse.move(hoverPos.x + x, hoverPos.y + y)
|
||||
await page.mouse.move(hoverPos.x, hoverPos.y, { steps: 5 })
|
||||
|
||||
await editor.expectEditor.toContain(lineOfInterest, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
lineOfInterest
|
||||
)
|
||||
|
||||
await page.getByTestId('overlay-menu').click()
|
||||
await page.waitForTimeout(100)
|
||||
@ -1205,9 +1197,9 @@ test.describe('Testing segment overlays', () => {
|
||||
await page.mouse.move(hoverPos.x + x, hoverPos.y + y)
|
||||
await page.mouse.move(hoverPos.x, hoverPos.y, { steps: 5 })
|
||||
|
||||
await editor.expectEditor.toContain(lineOfInterest, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
lineOfInterest
|
||||
)
|
||||
|
||||
await page.getByTestId('overlay-menu').click()
|
||||
await page.waitForTimeout(100)
|
||||
@ -1223,18 +1215,16 @@ test.describe('Testing segment overlays', () => {
|
||||
)
|
||||
).toBeTruthy()
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
await editor.expectEditor.toContain(lineOfInterest, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
lineOfInterest
|
||||
)
|
||||
} else {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
await editor.expectEditor.not.toContain(lineOfInterest, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toContainText(
|
||||
lineOfInterest
|
||||
)
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
await editor.expectEditor.not.toContain('seg01', {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toContainText('seg01')
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1305,18 +1295,16 @@ test.describe('Testing segment overlays', () => {
|
||||
const isObj = before.includes('{ angle = 3')
|
||||
test(`${before.split('(')[0]}${isObj ? '-[obj-input]' : ''}`, async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.addInitScript(
|
||||
async ({ lineToBeDeleted }) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([5, 6], %)
|
||||
|> ${lineToBeDeleted}
|
||||
|> line([-10, -15], %)
|
||||
|> angledLine([-176, segLen(seg01)], %)`
|
||||
|> startProfileAt([5, 6], %)
|
||||
|> ${lineToBeDeleted}
|
||||
|> line([-10, -15], %)
|
||||
|> angledLine([-176, segLen(seg01)], %)`
|
||||
)
|
||||
},
|
||||
{
|
||||
@ -1326,8 +1314,7 @@ test.describe('Testing segment overlays', () => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
await page.getByText(before).click()
|
||||
@ -1360,16 +1347,14 @@ test.describe('Testing segment overlays', () => {
|
||||
5,
|
||||
'[data-overlay-toolbar-index="0"]'
|
||||
)
|
||||
await page.mouse.move(x, y)
|
||||
|
||||
await editor.expectEditor.toContain(before, { shouldNormalise: true })
|
||||
await expect(page.locator('.cm-content')).toContainText(before)
|
||||
|
||||
await page.getByTestId('overlay-menu').click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByText('Remove constraints').click()
|
||||
|
||||
await editor.expectEditor.toContain(after, { shouldNormalise: true })
|
||||
|
||||
await expect(page.locator('.cm-content')).toContainText(after)
|
||||
// check the cursor was left in the correct place after transform
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText('|> ' + after)
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
|
||||
|
||||
@ -1,16 +1,24 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { commonPoints, getUtils } from './test-utils'
|
||||
import { commonPoints, getUtils, setup, tearDown } from './test-utils'
|
||||
import { Coords2d } from 'lang/std/sketch'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
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.setTimeout(90_000)
|
||||
test(
|
||||
'Selections work on fresh and edited sketch',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage }) => {
|
||||
async ({ page }) => {
|
||||
// Skip on windows its being weird.
|
||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||
|
||||
@ -19,9 +27,9 @@ test.describe('Testing selections', () => {
|
||||
// source ranges are wrong, hovers won't work
|
||||
const u = await getUtils(page)
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const yAxisClick = () =>
|
||||
@ -71,31 +79,31 @@ test.describe('Testing selections', () => {
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
|
||||
// deselect line tool
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
@ -256,78 +264,78 @@ test.describe('Testing selections', () => {
|
||||
}
|
||||
)
|
||||
|
||||
test('Solids should be select and deletable', async ({ page, homePage }) => {
|
||||
test('Solids should be select and deletable', async ({ page }) => {
|
||||
test.setTimeout(90_000)
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line([112.54, 127.64], %, $seg02)
|
||||
|> line([170.36, -121.61], %, $seg01)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(50, sketch001)
|
||||
sketch005 = startSketchOn(extrude001, 'END')
|
||||
|> startProfileAt([23.24, 136.52], %)
|
||||
|> line([-8.44, 36.61], %)
|
||||
|> line([49.4, 2.05], %)
|
||||
|> line([29.69, -46.95], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch003 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([21.23, 17.81], %)
|
||||
|> line([51.97, 21.32], %)
|
||||
|> line([4.07, -22.75], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch002 = startSketchOn(extrude001, seg02)
|
||||
|> startProfileAt([-100.54, 16.99], %)
|
||||
|> line([0, 20.03], %)
|
||||
|> line([62.61, 0], %, $seg03)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude002 = extrude(50, sketch002)
|
||||
sketch004 = startSketchOn(extrude002, seg03)
|
||||
|> startProfileAt([57.07, 134.77], %)
|
||||
|> line([-4.72, 22.84], %)
|
||||
|> line([28.8, 6.71], %)
|
||||
|> line([9.19, -25.33], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude003 = extrude(20, sketch004)
|
||||
pipeLength = 40
|
||||
pipeSmallDia = 10
|
||||
pipeLargeDia = 20
|
||||
thickness = 0.5
|
||||
part009 = startSketchOn('XY')
|
||||
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|
||||
|> line([thickness, 0], %)
|
||||
|> line([0, -1], %)
|
||||
|> angledLineToX({
|
||||
angle = 60,
|
||||
to = pipeSmallDia + thickness
|
||||
}, %)
|
||||
|> line([0, -pipeLength], %)
|
||||
|> angledLineToX({
|
||||
angle = -60,
|
||||
to = pipeLargeDia + thickness
|
||||
}, %)
|
||||
|> line([0, -1], %)
|
||||
|> line([-thickness, 0], %)
|
||||
|> line([0, 1], %)
|
||||
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|
||||
|> line([0, pipeLength], %)
|
||||
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
||||
|> close(%)
|
||||
rev = revolve({ axis: 'y' }, part009)
|
||||
`
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line([112.54, 127.64], %, $seg02)
|
||||
|> line([170.36, -121.61], %, $seg01)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(50, sketch001)
|
||||
sketch005 = startSketchOn(extrude001, 'END')
|
||||
|> startProfileAt([23.24, 136.52], %)
|
||||
|> line([-8.44, 36.61], %)
|
||||
|> line([49.4, 2.05], %)
|
||||
|> line([29.69, -46.95], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch003 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([21.23, 17.81], %)
|
||||
|> line([51.97, 21.32], %)
|
||||
|> line([4.07, -22.75], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch002 = startSketchOn(extrude001, seg02)
|
||||
|> startProfileAt([-100.54, 16.99], %)
|
||||
|> line([0, 20.03], %)
|
||||
|> line([62.61, 0], %, $seg03)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude002 = extrude(50, sketch002)
|
||||
sketch004 = startSketchOn(extrude002, seg03)
|
||||
|> startProfileAt([57.07, 134.77], %)
|
||||
|> line([-4.72, 22.84], %)
|
||||
|> line([28.8, 6.71], %)
|
||||
|> line([9.19, -25.33], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude003 = extrude(20, sketch004)
|
||||
pipeLength = 40
|
||||
pipeSmallDia = 10
|
||||
pipeLargeDia = 20
|
||||
thickness = 0.5
|
||||
part009 = startSketchOn('XY')
|
||||
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|
||||
|> line([thickness, 0], %)
|
||||
|> line([0, -1], %)
|
||||
|> angledLineToX({
|
||||
angle = 60,
|
||||
to = pipeSmallDia + thickness
|
||||
}, %)
|
||||
|> line([0, -pipeLength], %)
|
||||
|> angledLineToX({
|
||||
angle = -60,
|
||||
to = pipeLargeDia + thickness
|
||||
}, %)
|
||||
|> line([0, -1], %)
|
||||
|> line([-thickness, 0], %)
|
||||
|> line([0, 1], %)
|
||||
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|
||||
|> line([0, pipeLength], %)
|
||||
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
||||
|> close(%)
|
||||
rev = revolve({ axis = 'y' }, part009)
|
||||
`
|
||||
)
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
@ -387,29 +395,29 @@ test.describe('Testing selections', () => {
|
||||
`extrude001 = extrude(50, sketch001)`
|
||||
)
|
||||
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = 0, y = -50, z = 0 },
|
||||
x_axis = { x = 1, y = 0, z = 0 },
|
||||
y_axis = { x = 0, y = 0, z = 1 },
|
||||
z_axis = { x = 0, y = -1, z = 0 }
|
||||
}
|
||||
})`)
|
||||
plane = {
|
||||
origin = { x = 0, y = -50, z = 0 },
|
||||
x_axis = { x = 1, y = 0, z = 0 },
|
||||
y_axis = { x = 0, y = 0, z = 1 },
|
||||
z_axis = { x = 0, y = -1, z = 0 }
|
||||
}
|
||||
})`)
|
||||
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = 116.53, y = 0, z = 163.25 },
|
||||
x_axis = { x = -0.81, y = 0, z = 0.58 },
|
||||
y_axis = { x = 0, y = -1, z = 0 },
|
||||
z_axis = { x = 0.58, y = 0, z = 0.81 }
|
||||
}
|
||||
})`)
|
||||
plane = {
|
||||
origin = { x = 116.53, y = 0, z = 163.25 },
|
||||
x_axis = { x = -0.81, y = 0, z = 0.58 },
|
||||
y_axis = { x = 0, y = -1, z = 0 },
|
||||
z_axis = { x = 0.58, y = 0, z = 0.81 }
|
||||
}
|
||||
})`)
|
||||
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = -91.74, y = 0, z = 80.89 },
|
||||
x_axis = { x = -0.66, y = 0, z = -0.75 },
|
||||
y_axis = { x = 0, y = -1, z = 0 },
|
||||
z_axis = { x = -0.75, y = 0, z = 0.66 }
|
||||
}
|
||||
})`)
|
||||
plane = {
|
||||
origin = { x = -91.74, y = 0, z = 80.89 },
|
||||
x_axis = { x = -0.66, y = 0, z = -0.75 },
|
||||
y_axis = { x = 0, y = -1, z = 0 },
|
||||
z_axis = { x = -0.75, y = 0, z = 0.66 }
|
||||
}
|
||||
})`)
|
||||
|
||||
// DELETE SOLID 2D
|
||||
await page.mouse.click(solid2d.x, solid2d.y)
|
||||
@ -425,32 +433,31 @@ test.describe('Testing selections', () => {
|
||||
})
|
||||
test("Deleting solid that the AST mod can't handle results in a toast message", async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line([112.54, 127.64], %, $seg02)
|
||||
|> line([170.36, -121.61], %, $seg01)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(50, sketch001)
|
||||
launderExtrudeThroughVar = extrude001
|
||||
sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
||||
|> startProfileAt([-100.54, 16.99], %)
|
||||
|> line([0, 20.03], %)
|
||||
|> line([62.61, 0], %, $seg03)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line([112.54, 127.64], %, $seg02)
|
||||
|> line([170.36, -121.61], %, $seg01)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(50, sketch001)
|
||||
launderExtrudeThroughVar = extrude001
|
||||
sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
||||
|> startProfileAt([-100.54, 16.99], %)
|
||||
|> line([0, 20.03], %)
|
||||
|> line([62.61, 0], %, $seg03)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
@ -490,38 +497,37 @@ test.describe('Testing selections', () => {
|
||||
})
|
||||
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([20, 0], %)
|
||||
|> line([7.13, 4 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 3.14 + 0 }, %)
|
||||
|> lineTo([20.14 + 0, -0.14 + 0], %)
|
||||
|> xLineTo(29 + 0, %)
|
||||
|> yLine(-3.14 + 0, %, $a)
|
||||
|> xLine(1.63, %)
|
||||
|> angledLineOfXLength({ angle = 3 + 0, length = 3.14 }, %)
|
||||
|> angledLineOfYLength({ angle = 30, length = 3 + 0 }, %)
|
||||
|> angledLineToX({ angle = 22.14 + 0, to = 12 }, %)
|
||||
|> angledLineToY({ angle = 30, to = 11.14 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 3.14,
|
||||
intersectTag = a,
|
||||
offset = 0
|
||||
}, %)
|
||||
|> tangentialArcTo([13.14 + 0, 13.14], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
`
|
||||
|> startProfileAt([20, 0], %)
|
||||
|> line([7.13, 4 + 0], %)
|
||||
|> angledLine({ angle = 3 + 0, length = 3.14 + 0 }, %)
|
||||
|> lineTo([20.14 + 0, -0.14 + 0], %)
|
||||
|> xLineTo(29 + 0, %)
|
||||
|> yLine(-3.14 + 0, %, $a)
|
||||
|> xLine(1.63, %)
|
||||
|> angledLineOfXLength({ angle = 3 + 0, length = 3.14 }, %)
|
||||
|> angledLineOfYLength({ angle = 30, length = 3 + 0 }, %)
|
||||
|> angledLineToX({ angle = 22.14 + 0, to = 12 }, %)
|
||||
|> angledLineToY({ angle = 30, to = 11.14 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 3.14,
|
||||
intersectTag = a,
|
||||
offset = 0
|
||||
}, %)
|
||||
|> tangentialArcTo([13.14 + 0, 13.14], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
`
|
||||
)
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -721,29 +727,29 @@ test.describe('Testing selections', () => {
|
||||
|
||||
await u.removeCurrentCode()
|
||||
await u.codeLocator.fill(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
217.26
|
||||
], %, $seg01)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $yo)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %, $seg02)
|
||||
|> close(%)
|
||||
extrude001 = extrude(100, sketch001)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [
|
||||
seg01,
|
||||
getNextAdjacentEdge(yo),
|
||||
getNextAdjacentEdge(seg02),
|
||||
getOppositeEdge(seg01)
|
||||
]
|
||||
}, %)
|
||||
`)
|
||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
217.26
|
||||
], %, $seg01)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $yo)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %, $seg02)
|
||||
|> close(%)
|
||||
extrude001 = extrude(100, sketch001)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [
|
||||
seg01,
|
||||
getNextAdjacentEdge(yo),
|
||||
getNextAdjacentEdge(seg02),
|
||||
getOppositeEdge(seg01)
|
||||
]
|
||||
}, %)
|
||||
`)
|
||||
await expect(
|
||||
page.getByTestId('model-state-indicator-execution-done')
|
||||
).toBeVisible()
|
||||
@ -831,45 +837,39 @@ test.describe('Testing selections', () => {
|
||||
})
|
||||
test("Extrude button should be disabled if there's no extrudable geometry when nothing is selected", async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> line([3.75, 0.46], %)
|
||||
|> line([4.99, -0.46], %, $seg01)
|
||||
|> line([3.3, -2.12], %)
|
||||
|> line([2.16, -3.33], %)
|
||||
|> line([0.85, -3.08], %)
|
||||
|> line([-0.18, -3.36], %)
|
||||
|> line([-3.86, -2.73], %)
|
||||
|> line([-17.67, 0.85], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(10, sketch001)
|
||||
`
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> line([3.75, 0.46], %)
|
||||
|> line([4.99, -0.46], %, $seg01)
|
||||
|> line([3.3, -2.12], %)
|
||||
|> line([2.16, -3.33], %)
|
||||
|> line([0.85, -3.08], %)
|
||||
|> line([-0.18, -3.36], %)
|
||||
|> line([-3.86, -2.73], %)
|
||||
|> line([-17.67, 0.85], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(10, sketch001)
|
||||
`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const selectUnExtrudable = async () => {
|
||||
await editor.scrollToText(`line([4.99, -0.46], %, $seg01)`)
|
||||
await page.getByText(`line([4.99, -0.46], %, $seg01)`).click()
|
||||
}
|
||||
const selectUnExtrudable = () =>
|
||||
page.getByText(`line([4.99, -0.46], %, $seg01)`).click()
|
||||
const clickEmpty = () => page.mouse.click(700, 460)
|
||||
await selectUnExtrudable()
|
||||
// expect extrude button to be disabled
|
||||
@ -879,18 +879,17 @@ test.describe('Testing selections', () => {
|
||||
|
||||
// expect active line to contain nothing
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText('')
|
||||
|
||||
// and extrude to still be disabled
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||
|
||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||
sketch002 = startSketchOn(extrude001, $seg01)
|
||||
|> startProfileAt([-12.94, 6.6], %)
|
||||
|> line([2.45, -0.2], %)
|
||||
|> line([-2, -1.25], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
sketch002 = startSketchOn(extrude001, $seg01)
|
||||
|> startProfileAt([-12.94, 6.6], %)
|
||||
|> line([2.45, -0.2], %)
|
||||
|> line([-2, -1.25], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`
|
||||
await u.codeLocator.fill(codeToAdd)
|
||||
|
||||
await selectUnExtrudable()
|
||||
@ -905,23 +904,23 @@ test.describe('Testing selections', () => {
|
||||
).not.toBeDisabled()
|
||||
})
|
||||
|
||||
test('Fillet button states test', async ({ page, homePage }) => {
|
||||
test('Fillet button states test', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
@ -938,7 +937,7 @@ test.describe('Testing selections', () => {
|
||||
|
||||
// test fillet button with the body in the scene
|
||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||
extrude001 = extrude(10, sketch001)`
|
||||
extrude001 = extrude(10, sketch001)`
|
||||
await u.codeLocator.clear()
|
||||
await u.codeLocator.fill(codeToAdd)
|
||||
await selectSegment()
|
||||
@ -959,7 +958,6 @@ test.describe('Testing selections', () => {
|
||||
|
||||
test('Testing selections (and hovers) work on sketches when NOT in sketch mode', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const cases = [
|
||||
{
|
||||
@ -980,21 +978,21 @@ test.describe('Testing selections', () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 79
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> ${cases[0].expectedCode}
|
||||
|> line([-3.19, -138.43], %)
|
||||
|> ${cases[1].expectedCode}
|
||||
|> line([41.19, 28.97 + 5], %)
|
||||
|> ${cases[2].expectedCode}`
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> ${cases[0].expectedCode}
|
||||
|> line([-3.19, -138.43], %)
|
||||
|> ${cases[1].expectedCode}
|
||||
|> line([41.19, 28.97 + 5], %)
|
||||
|> ${cases[2].expectedCode}`
|
||||
)
|
||||
},
|
||||
{ cases }
|
||||
)
|
||||
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 u.openAndClearDebugPanel()
|
||||
|
||||
await u.sendCustomCmd({
|
||||
@ -1027,25 +1025,24 @@ test.describe('Testing selections', () => {
|
||||
})
|
||||
test("Hovering and selection of extruded faces works, and is not overridden shortly after user's click", async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line([112.54, 127.64], %)
|
||||
|> line([170.36, -121.61], %, $seg01)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line([112.54, 127.64], %)
|
||||
|> line([170.36, -121.61], %, $seg01)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
)
|
||||
})
|
||||
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 u.openAndClearDebugPanel()
|
||||
|
||||
await u.sendCustomCmd({
|
||||
@ -1128,7 +1125,6 @@ test.describe('Testing selections', () => {
|
||||
})
|
||||
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
const selectionsSnippets = {
|
||||
@ -1147,46 +1143,46 @@ test.describe('Testing selections', () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
${extrudeAndEditBlocked}
|
||||
|> line([25.96, 2.93], %)
|
||||
|> line([5.25, -5.72], %)
|
||||
|> line([-2.01, -10.35], %)
|
||||
|> line([-27.65, -2.78], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
sketch002 = startSketchOn('XZ')
|
||||
${extrudeAndEditAllowed}
|
||||
|> line([10.32, 6.47], %)
|
||||
|> line([9.71, -6.16], %)
|
||||
|> line([-3.08, -9.86], %)
|
||||
|> line([-12.02, -1.54], %)
|
||||
|> close(%)
|
||||
sketch003 = startSketchOn('XZ')
|
||||
${editOnly}
|
||||
|> line([27.55, -1.65], %)
|
||||
|> line([4.95, -8], %)
|
||||
|> line([-20.38, -10.12], %)
|
||||
|> line([-15.79, 17.08], %)
|
||||
|
||||
fn yohey = (pos) => {
|
||||
sketch004 = startSketchOn('XZ')
|
||||
${extrudeAndEditBlockedInFunction}
|
||||
|> line([27.55, -1.65], %)
|
||||
|> line([4.95, -10.53], %)
|
||||
|> line([-20.38, -8], %)
|
||||
|> line([-15.79, 17.08], %)
|
||||
return ''
|
||||
}
|
||||
|
||||
yohey([15.79, -34.6])
|
||||
`
|
||||
${extrudeAndEditBlocked}
|
||||
|> line([25.96, 2.93], %)
|
||||
|> line([5.25, -5.72], %)
|
||||
|> line([-2.01, -10.35], %)
|
||||
|> line([-27.65, -2.78], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
sketch002 = startSketchOn('XZ')
|
||||
${extrudeAndEditAllowed}
|
||||
|> line([10.32, 6.47], %)
|
||||
|> line([9.71, -6.16], %)
|
||||
|> line([-3.08, -9.86], %)
|
||||
|> line([-12.02, -1.54], %)
|
||||
|> close(%)
|
||||
sketch003 = startSketchOn('XZ')
|
||||
${editOnly}
|
||||
|> line([27.55, -1.65], %)
|
||||
|> line([4.95, -8], %)
|
||||
|> line([-20.38, -10.12], %)
|
||||
|> line([-15.79, 17.08], %)
|
||||
|
||||
fn yohey = (pos) => {
|
||||
sketch004 = startSketchOn('XZ')
|
||||
${extrudeAndEditBlockedInFunction}
|
||||
|> line([27.55, -1.65], %)
|
||||
|> line([4.95, -10.53], %)
|
||||
|> line([-20.38, -8], %)
|
||||
|> line([-15.79, 17.08], %)
|
||||
return ''
|
||||
}
|
||||
|
||||
yohey([15.79, -34.6])
|
||||
`
|
||||
)
|
||||
},
|
||||
selectionsSnippets
|
||||
)
|
||||
await page.setBodyDimensions({ width: 1200, height: 1000 })
|
||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -1226,7 +1222,6 @@ test.describe('Testing selections', () => {
|
||||
|
||||
test('Deselecting line tool should mean nothing happens on click', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
/**
|
||||
* If the line tool is clicked when the state is 'No Points' it will exit Sketch mode.
|
||||
@ -1235,9 +1230,9 @@ test.describe('Testing selections', () => {
|
||||
* 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)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
await expect(
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { getUtils, executorInputPath, createProject } from './test-utils'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
createProject,
|
||||
} from './test-utils'
|
||||
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
|
||||
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
|
||||
import {
|
||||
@ -12,136 +19,141 @@ import {
|
||||
} 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('Testing settings', () => {
|
||||
test(
|
||||
'Stored settings are validated and fall back to defaults',
|
||||
test('Stored settings are validated and fall back to defaults', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Override beforeEach test setup
|
||||
// with corrupted settings
|
||||
{
|
||||
appSettings: TEST_SETTINGS_CORRUPTED,
|
||||
},
|
||||
async ({ page, homePage }) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_CORRUPTED }),
|
||||
}
|
||||
)
|
||||
|
||||
// Check the settings were reset
|
||||
const storedSettings = TOML.parse(
|
||||
await page.evaluate(
|
||||
({ settingsKey }) => localStorage.getItem(settingsKey) || '',
|
||||
{ settingsKey: TEST_SETTINGS_KEY }
|
||||
)
|
||||
) as { settings: SaveSettingsPayload }
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
expect(storedSettings.settings?.app?.theme).toBe('dark')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Check that the invalid settings were changed to good defaults
|
||||
expect(storedSettings.settings?.modeling?.defaultUnit).toBe('in')
|
||||
expect(storedSettings.settings?.modeling?.mouseControls).toBe('KittyCAD')
|
||||
expect(storedSettings.settings?.app?.projectDirectory).toBe('')
|
||||
expect(storedSettings.settings?.projects?.defaultProjectName).toBe(
|
||||
'project-$nnn'
|
||||
// Check the settings were reset
|
||||
const storedSettings = TOML.parse(
|
||||
await page.evaluate(
|
||||
({ settingsKey }) => localStorage.getItem(settingsKey) || '',
|
||||
{ settingsKey: TEST_SETTINGS_KEY }
|
||||
)
|
||||
}
|
||||
)
|
||||
) as { settings: SaveSettingsPayload }
|
||||
|
||||
// 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' })
|
||||
})
|
||||
expect(storedSettings.settings?.app?.theme).toBe(undefined)
|
||||
|
||||
// Selectors and constants
|
||||
const paneButtonLocator = page.getByTestId('debug-pane-button')
|
||||
const headingLocator = page.getByRole('heading', {
|
||||
name: 'Settings',
|
||||
exact: true,
|
||||
})
|
||||
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
|
||||
// Check that the invalid settings were removed
|
||||
expect(storedSettings.settings?.modeling?.defaultUnit).toBe(undefined)
|
||||
expect(storedSettings.settings?.modeling?.mouseControls).toBe(undefined)
|
||||
expect(storedSettings.settings?.app?.projectDirectory).toBe(undefined)
|
||||
expect(storedSettings.settings?.projects?.defaultProjectName).toBe(
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('Open settings dialog and set "Show debug panel" to on', async () => {
|
||||
await page.keyboard.press('ControlOrMeta+,')
|
||||
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+,')
|
||||
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()
|
||||
test('Project settings can be set and override user settings', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await test.step(`Setup`, async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Roll back show debug panel',
|
||||
})
|
||||
.click()
|
||||
await expect(inputLocator).not.toBeChecked()
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
})
|
||||
|
||||
// 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()
|
||||
}
|
||||
)
|
||||
// Selectors and constants
|
||||
const paneButtonLocator = page.getByTestId('debug-pane-button')
|
||||
const headingLocator = page.getByRole('heading', {
|
||||
name: 'Settings',
|
||||
exact: true,
|
||||
})
|
||||
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 ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await test.step('Open keybindings settings', async () => {
|
||||
// Open the settings modal with the keyboard shortcut
|
||||
await page.keyboard.press('ControlOrMeta+,')
|
||||
// Open the settings modal with the browser keyboard shortcut
|
||||
await page.keyboard.press('ControlOrMeta+Shift+,')
|
||||
|
||||
// Go to Keybindings tab.
|
||||
const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' })
|
||||
@ -162,13 +174,11 @@ test.describe('Testing settings', () => {
|
||||
await expect(hotkey).toHaveText(text)
|
||||
})
|
||||
|
||||
test('Project and user settings can be reset', async ({ page, homePage }) => {
|
||||
test('Project and user settings can be reset', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await test.step(`Setup`, async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.waitForTimeout(1000)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
})
|
||||
|
||||
// Selectors and constants
|
||||
@ -251,24 +261,29 @@ test.describe('Testing settings', () => {
|
||||
test.fixme(
|
||||
`Project settings override user settings on desktop`,
|
||||
{ tag: ['@electron', '@skipWin'] },
|
||||
async ({ context, page }, testInfo) => {
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const projectName = 'bracket'
|
||||
const { dir: projectDirName } = await context.folderSetupFn(
|
||||
async (dir) => {
|
||||
const {
|
||||
electronApp,
|
||||
page,
|
||||
dir: projectDirName,
|
||||
} = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, projectName)
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Selectors and constants
|
||||
const tempProjectSettingsFilePath = join(
|
||||
@ -338,18 +353,22 @@ test.describe('Testing settings', () => {
|
||||
await logoLink.click()
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`Load desktop app with no settings file`,
|
||||
{
|
||||
tag: '@electron',
|
||||
// This is what makes no settings file get created
|
||||
cleanProjectDir: false,
|
||||
},
|
||||
async ({ page }, testInfo) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
// This is what makes no settings file get created
|
||||
cleanProjectDir: false,
|
||||
testInfo,
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Selectors and constants
|
||||
const errorHeading = page.getByRole('heading', {
|
||||
@ -360,21 +379,25 @@ test.describe('Testing settings', () => {
|
||||
// If the app loads without exploding we're in the clear
|
||||
await expect(errorHeading).not.toBeVisible()
|
||||
await expect(projectDirLink).toBeVisible()
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`Load desktop app with a settings file, but no project directory setting`,
|
||||
{
|
||||
tag: '@electron',
|
||||
appSettings: {
|
||||
app: {
|
||||
themeColor: '259',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
appSettings: {
|
||||
app: {
|
||||
themeColor: '259',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async ({ context, page }, testInfo) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Selectors and constants
|
||||
const errorHeading = page.getByRole('heading', {
|
||||
@ -385,28 +408,32 @@ test.describe('Testing settings', () => {
|
||||
// If the app loads without exploding we're in the clear
|
||||
await expect(errorHeading).not.toBeVisible()
|
||||
await expect(projectDirLink).toBeVisible()
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
// It was much easier to test the logo color than the background stream color.
|
||||
test.fixme(
|
||||
'user settings reload on external change, on project and modeling view',
|
||||
{
|
||||
tag: '@electron',
|
||||
appSettings: {
|
||||
app: {
|
||||
// Doesn't matter what you set it to. It will
|
||||
// default to 264.5
|
||||
themeColor: '0',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const {
|
||||
electronApp,
|
||||
page,
|
||||
dir: projectDirName,
|
||||
} = await setupElectron({
|
||||
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.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
const logoLink = page.getByTestId('app-logo')
|
||||
const projectDirLink = page.getByText('Loaded from')
|
||||
@ -440,18 +467,23 @@ test.describe('Testing settings', () => {
|
||||
await changeColor('21')
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', '21')
|
||||
})
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme(
|
||||
test(
|
||||
'project settings reload on external change',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
const { dir: projectDirName } = await context.folderSetupFn(
|
||||
async () => {}
|
||||
)
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const {
|
||||
electronApp,
|
||||
page,
|
||||
dir: projectDirName,
|
||||
} = await setupElectron({
|
||||
testInfo,
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
const logoLink = page.getByTestId('app-logo')
|
||||
const projectDirLink = page.getByText('Loaded from')
|
||||
@ -482,24 +514,29 @@ test.describe('Testing settings', () => {
|
||||
await changeColorFs('99')
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', '99')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`Closing settings modal should go back to the original file being viewed`,
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'project-000')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(bracketDir, '2.kcl')
|
||||
)
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'project-000')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(bracketDir, '2.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8')
|
||||
const kclCylinder = await fsp.readFile(
|
||||
@ -515,7 +552,7 @@ test.describe('Testing settings', () => {
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await test.step('Precondition: Open to second project file', async () => {
|
||||
@ -546,15 +583,16 @@ test.describe('Testing settings', () => {
|
||||
await test.step('Postcondition: Same file content is in editor as before settings opened', async () => {
|
||||
await editorTextMatches(kclCylinder)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test('Changing modeling default unit', async ({ page, homePage }) => {
|
||||
test('Changing modeling default unit', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await test.step(`Test setup`, async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
const toastMessage = page.getByText(`Successfully created "testDefault"`)
|
||||
await expect(toastMessage).not.toBeVisible()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
@ -581,9 +619,7 @@ test.describe('Testing settings', () => {
|
||||
await userSettingsTab.click()
|
||||
await defaultUnitSection.hover()
|
||||
await defaultUnitRollbackButton.click()
|
||||
await projectSettingsTab.hover()
|
||||
await projectSettingsTab.click()
|
||||
await page.waitForTimeout(1000)
|
||||
})
|
||||
|
||||
await test.step('Change modeling default unit within project tab', async () => {
|
||||
@ -595,10 +631,7 @@ test.describe('Testing settings', () => {
|
||||
const toastMessage = page.getByText(
|
||||
`Set default unit to "${unitOfMeasure}" for this project`
|
||||
)
|
||||
|
||||
// Assert visibility and disapperance
|
||||
await expect(toastMessage).toBeVisible()
|
||||
await expect(toastMessage).not.toBeVisible()
|
||||
})
|
||||
}
|
||||
await changeUnitOfMeasureInProjectTab('in')
|
||||
@ -610,10 +643,7 @@ test.describe('Testing settings', () => {
|
||||
})
|
||||
|
||||
// Go to the user tab
|
||||
await userSettingsTab.hover()
|
||||
await userSettingsTab.click()
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
await test.step('Change modeling default unit within user tab', async () => {
|
||||
const changeUnitOfMeasureInUserTab = async (unitOfMeasure: string) => {
|
||||
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
|
||||
@ -696,26 +726,23 @@ test.describe('Testing settings', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('Changing theme in sketch mode', async ({ context, page, homePage }) => {
|
||||
test('Changing theme in sketch mode', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(() => {
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([5, 0], %)
|
||||
|> line([0, 5], %)
|
||||
|> line([-5, 0], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)
|
||||
`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([5, 0], %)
|
||||
|> line([0, 5], %)
|
||||
|> line([-5, 0], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, sketch001)
|
||||
`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.waitForTimeout(1000)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Selectors and constants
|
||||
const editSketchButton = page.getByRole('button', { name: 'Edit Sketch' })
|
||||
@ -726,6 +753,7 @@ test.describe('Testing settings', () => {
|
||||
const lightThemeSegmentColor: [number, number, number] = [90, 90, 90]
|
||||
|
||||
await test.step(`Get into sketch mode`, async () => {
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page.mouse.click(700, 200)
|
||||
await expect(editSketchButton).toBeVisible()
|
||||
await editSketchButton.click()
|
||||
@ -764,125 +792,135 @@ test.describe('Testing settings', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test(
|
||||
`Changing system theme preferences (via media query) should update UI and stream`,
|
||||
{
|
||||
// Override the settings so that the theme is set to `system`
|
||||
appSettings: TEST_SETTINGS_DEFAULT_THEME,
|
||||
},
|
||||
async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Selectors and constants
|
||||
const darkBackgroundCss = 'oklch(0.3012 0 264.5)'
|
||||
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' })
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(toolbar).toBeVisible()
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`Turning off "Show debug panel" with debug panel open leaves no phantom panel`,
|
||||
{
|
||||
// Override beforeEach test setup
|
||||
// with debug panel open
|
||||
// but "show debug panel" set to false
|
||||
appSettings: {
|
||||
...TEST_SETTINGS,
|
||||
modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false },
|
||||
test(`Changing system theme preferences (via media query) should update UI and stream`, async ({
|
||||
page,
|
||||
}) => {
|
||||
// Override the settings so that the theme is set to `system`
|
||||
await page.addInitScript(
|
||||
({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
},
|
||||
async ({ context, page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: TEST_SETTINGS_DEFAULT_THEME,
|
||||
}),
|
||||
}
|
||||
)
|
||||
const u = await getUtils(page)
|
||||
|
||||
await context.addInitScript(async () => {
|
||||
// Selectors and constants
|
||||
const darkBackgroundCss = 'oklch(0.3012 0 264.5)'
|
||||
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' })
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(toolbar).toBeVisible()
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
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(
|
||||
'persistModelingContext',
|
||||
'{"openPanes":["debug"]}'
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Constants and locators
|
||||
const resizeHandle = page.locator('.sidebar-resize-handles > div.block')
|
||||
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()
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({
|
||||
settings: {
|
||||
...TEST_SETTINGS,
|
||||
modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false },
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await test.step(`Initial load with corrupted settings`, async () => {
|
||||
// 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()
|
||||
})
|
||||
// Constants and locators
|
||||
const resizeHandle = page.locator('.sidebar-resize-handles > div.block')
|
||||
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',
|
||||
})
|
||||
|
||||
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()
|
||||
})
|
||||
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 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,16 +1,29 @@
|
||||
import { test, expect, Page } from './zoo-test'
|
||||
import { getUtils, createProject } from './test-utils'
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
tearDown,
|
||||
setupElectron,
|
||||
createProject,
|
||||
} from './test-utils'
|
||||
import { join } from 'path'
|
||||
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('basic lego happy case', async ({ page, homePage }) => {
|
||||
test('basic lego happy case', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await test.step('Set up', async () => {
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
})
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
@ -30,17 +43,25 @@ test.describe('Text-to-CAD tests', () => {
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
|
||||
// Hit accept.
|
||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||
|
||||
// Hit copy to clipboard.
|
||||
const copyToClipboardButton = page.getByRole('button', {
|
||||
name: 'Accept',
|
||||
name: 'Copy to clipboard',
|
||||
})
|
||||
await expect(copyToClipboardButton).toBeVisible()
|
||||
|
||||
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()
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.press('ControlOrMeta+KeyV')
|
||||
|
||||
// Expect the code to be pasted.
|
||||
await expect(page.locator('.cm-content')).toContainText(`const`)
|
||||
|
||||
@ -49,18 +70,29 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
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 ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x6 lego')
|
||||
|
||||
@ -79,6 +111,10 @@ test.describe('Text-to-CAD tests', () => {
|
||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||
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.
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
@ -97,14 +133,12 @@ test.describe('Text-to-CAD tests', () => {
|
||||
|
||||
test('you can reject text-to-cad output and it does nothing', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
@ -136,16 +170,12 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await expect(page.locator('.cm-content')).toContainText(``)
|
||||
})
|
||||
|
||||
test('sending a bad prompt fails, can dismiss', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('sending a bad prompt fails, can dismiss', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandBarButton).toBeVisible()
|
||||
@ -206,14 +236,12 @@ test.describe('Text-to-CAD tests', () => {
|
||||
|
||||
test('sending a bad prompt fails, can start over from toast', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandBarButton).toBeVisible()
|
||||
@ -296,14 +324,12 @@ test.describe('Text-to-CAD tests', () => {
|
||||
|
||||
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandBarButton).toBeVisible()
|
||||
@ -365,21 +391,19 @@ test.describe('Text-to-CAD tests', () => {
|
||||
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
|
||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||
|
||||
// old failure toast should stick around.
|
||||
await expect(failureToastMessage).toBeVisible()
|
||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||
})
|
||||
|
||||
test('ensure you can shift+enter in the prompt box', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test('ensure you can shift+enter in the prompt box', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const promptWithNewline = `a 2x4\nlego`
|
||||
|
||||
@ -432,7 +456,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test(
|
||||
'can do many at once and get many prompts back, and interact with many',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage }) => {
|
||||
async ({ page }) => {
|
||||
// Let this test run longer since we've seen it timeout.
|
||||
test.setTimeout(180_000)
|
||||
// skip on windows
|
||||
@ -443,10 +467,9 @@ test.describe('Text-to-CAD tests', () => {
|
||||
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
@ -472,6 +495,8 @@ test.describe('Text-to-CAD tests', () => {
|
||||
// We should have three success toasts.
|
||||
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 2x8 lego`)).toBeVisible()
|
||||
await expect(page.getByText(`a 2x10 lego`)).toBeVisible()
|
||||
@ -489,15 +514,31 @@ test.describe('Text-to-CAD tests', () => {
|
||||
|
||||
// Ensure you can copy the code for one of the models remaining.
|
||||
const copyToClipboardButton = page.getByRole('button', {
|
||||
name: 'Accept',
|
||||
name: 'Copy to clipboard',
|
||||
})
|
||||
await expect(copyToClipboardButton.first()).toBeVisible()
|
||||
// Click the button.
|
||||
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.
|
||||
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.
|
||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
|
||||
@ -508,21 +549,40 @@ test.describe('Text-to-CAD tests', () => {
|
||||
// Click the button.
|
||||
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.
|
||||
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 ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
||||
|
||||
@ -571,37 +631,57 @@ test.describe('Text-to-CAD tests', () => {
|
||||
|
||||
// Ensure you can copy the code for one of the models remaining.
|
||||
const copyToClipboardButton = page.getByRole('button', {
|
||||
name: 'Accept',
|
||||
name: 'Copy to clipboard',
|
||||
})
|
||||
await expect(copyToClipboardButton.first()).toBeVisible()
|
||||
// Click the button.
|
||||
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.
|
||||
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) {
|
||||
await page.waitForTimeout(1000)
|
||||
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandBarButton).toBeVisible()
|
||||
// Click the command bar button
|
||||
await commandBarButton.hover()
|
||||
await commandBarButton.click()
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Wait for the command bar to appear
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
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()
|
||||
// Click the Text-to-CAD command
|
||||
await textToCadCommand.first().scrollIntoViewIfNeeded()
|
||||
await textToCadCommand.first().click()
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Enter the prompt.
|
||||
const prompt = page.getByText('Prompt')
|
||||
@ -617,13 +697,12 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
||||
test(
|
||||
'Text-to-CAD functionality',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
async ({ browserName }, testInfo) => {
|
||||
const projectName = 'project-000'
|
||||
const prompt = 'lego 2x4'
|
||||
const textToCadFileName = 'lego-2x4.kcl'
|
||||
|
||||
const { dir } = await context.folderSetupFn(async () => {})
|
||||
|
||||
const { electronApp, page, dir } = await setupElectron({ testInfo })
|
||||
const fileExists = () =>
|
||||
fs.existsSync(join(dir, projectName, textToCadFileName))
|
||||
|
||||
@ -632,7 +711,7 @@ test(
|
||||
test
|
||||
)
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Locators
|
||||
const projectMenuButton = page
|
||||
@ -682,5 +761,7 @@ test(
|
||||
// Confirm we've navigated back to the main.kcl file after deletion
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
@ -1,10 +1,19 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { doExport, getUtils, makeTemplate } from './test-utils'
|
||||
import { doExport, getUtils, makeTemplate, setup, tearDown } from './test-utils'
|
||||
|
||||
test.fixme('Units menu', async ({ page, homePage }) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
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', {
|
||||
name: 'Current Units',
|
||||
@ -32,7 +41,7 @@ test.fixme('Units menu', async ({ page, homePage }) => {
|
||||
await expect(unitsMenuButton).toContainText('mm')
|
||||
})
|
||||
|
||||
test('Successful export shows a success toast', async ({ page, homePage }) => {
|
||||
test('Successful export shows a success toast', async ({ page }) => {
|
||||
// FYI this test doesn't work with only engine running locally
|
||||
// And you will need to have the KittyCAD CLI installed
|
||||
const u = await getUtils(page)
|
||||
@ -48,41 +57,41 @@ totalHeightHalf = 2
|
||||
armThick = 0.5
|
||||
totalLen = 9.5
|
||||
part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> yLine(baseHeight, %)
|
||||
|> xLine(baseLen, %)
|
||||
|> angledLineToY({
|
||||
angle = topAng,
|
||||
to = totalHeightHalf,
|
||||
}, %, $seg04)
|
||||
|> xLineTo(totalLen, %, $seg03)
|
||||
|> yLine(-armThick, %, $seg01)
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg04
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg04) + 180, ZERO], %)
|
||||
|> angledLineToY({
|
||||
angle = -bottomAng,
|
||||
to = -totalHeightHalf - armThick,
|
||||
}, %, $seg02)
|
||||
|> xLineTo(segEndX(seg03) + 0, %)
|
||||
|> yLine(-segLen(seg01), %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg02
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
|
||||
|> xLineTo(ZERO, %)
|
||||
|> close(%)
|
||||
|> extrude(4, %)`
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> yLine(baseHeight, %)
|
||||
|> xLine(baseLen, %)
|
||||
|> angledLineToY({
|
||||
angle: topAng,
|
||||
to: totalHeightHalf,
|
||||
}, %, $seg04)
|
||||
|> xLineTo(totalLen, %, $seg03)
|
||||
|> yLine(-armThick, %, $seg01)
|
||||
|> angledLineThatIntersects({
|
||||
angle: HALF_TURN,
|
||||
offset: -armThick,
|
||||
intersectTag: seg04
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg04) + 180, ZERO], %)
|
||||
|> angledLineToY({
|
||||
angle: -bottomAng,
|
||||
to: -totalHeightHalf - armThick,
|
||||
}, %, $seg02)
|
||||
|> xLineTo(segEndX(seg03) + 0, %)
|
||||
|> yLine(-segLen(seg01), %)
|
||||
|> angledLineThatIntersects({
|
||||
angle: HALF_TURN,
|
||||
offset: -armThick,
|
||||
intersectTag: seg02
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
|
||||
|> xLineTo(ZERO, %)
|
||||
|> close(%)
|
||||
|> extrude(4, %)`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.waitForCmdReceive('extrude')
|
||||
@ -97,14 +106,25 @@ part001 = startSketchOn('-XZ')
|
||||
},
|
||||
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 ({
|
||||
page,
|
||||
homePage,
|
||||
browserName,
|
||||
}) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
// To run this test locally, uncomment Firefox in playwright.config.ts
|
||||
test.skip(
|
||||
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
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
@ -144,12 +164,12 @@ test('Paste should not work unless an input is focused', async ({
|
||||
|
||||
test('Keyboard shortcuts can be viewed through the help menu', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' })
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
@ -161,7 +181,7 @@ test('Keyboard shortcuts can be viewed through the help menu', async ({
|
||||
await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click()
|
||||
|
||||
// Verify the URL and that you can see a list of shortcuts
|
||||
await expect.poll(() => page.url()).toContain('?tab=keybindings')
|
||||
await expect(page.url()).toContain('?tab=keybindings')
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Enter Sketch Mode' })
|
||||
).toBeAttached()
|
||||
@ -169,13 +189,12 @@ 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 ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// Wait for the app to be ready for use
|
||||
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 u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
@ -239,7 +258,7 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
||||
|
||||
test.fixme(
|
||||
'Basic default modeling and sketch hotkeys work',
|
||||
async ({ page, homePage }) => {
|
||||
async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// This test can run long if it takes a little too long to load
|
||||
@ -266,8 +285,8 @@ test.fixme(
|
||||
})
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
@ -418,11 +437,10 @@ test.fixme(
|
||||
}
|
||||
)
|
||||
|
||||
test('Delete key does not navigate back', async ({ page, homePage }) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' })
|
||||
test('Delete key does not navigate back', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
const settingsButton = page.getByRole('link', {
|
||||
name: 'Settings',
|
||||
@ -431,45 +449,45 @@ test('Delete key does not navigate back', async ({ page, homePage }) => {
|
||||
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||
|
||||
await settingsButton.click()
|
||||
await expect.poll(() => page.url()).toContain('/settings')
|
||||
await expect(page.url()).toContain('/settings')
|
||||
|
||||
// Make sure that delete doesn't go back from settings
|
||||
await page.keyboard.press('Delete')
|
||||
await expect.poll(() => page.url()).toContain('/settings')
|
||||
await expect(page.url()).toContain('/settings')
|
||||
|
||||
// Now close the settings and try delete again,
|
||||
// make sure it doesn't go back to settings
|
||||
await settingsCloseButton.click()
|
||||
await page.keyboard.press('Delete')
|
||||
await expect.poll(() => page.url()).not.toContain('/settings')
|
||||
await expect(page.url()).not.toContain('/settings')
|
||||
})
|
||||
|
||||
test('Sketch on face', async ({ page, homePage }) => {
|
||||
test('Sketch on face', async ({ page }) => {
|
||||
test.setTimeout(90_000)
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> line([3.75, 0.46], %)
|
||||
|> line([4.99, -0.46], %)
|
||||
|> line([3.3, -2.12], %)
|
||||
|> line([2.16, -3.33], %)
|
||||
|> line([0.85, -3.08], %)
|
||||
|> line([-0.18, -3.36], %)
|
||||
|> line([-3.86, -2.73], %)
|
||||
|> line([-17.67, 0.85], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5 + 7, sketch001)`
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> line([3.75, 0.46], %)
|
||||
|> line([4.99, -0.46], %)
|
||||
|> line([3.3, -2.12], %)
|
||||
|> line([2.16, -3.33], %)
|
||||
|> line([0.85, -3.08], %)
|
||||
|> line([-0.18, -3.36], %)
|
||||
|> line([-3.86, -2.73], %)
|
||||
|> line([-17.67, 0.85], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5 + 7, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
@ -523,8 +541,7 @@ extrude001 = extrude(5 + 7, sketch001)`
|
||||
|> line([2.45, -0.2], %)
|
||||
|> line([-2.6, -1.25], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`)
|
||||
|> close(%)`)
|
||||
)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
@ -539,7 +556,7 @@ extrude001 = extrude(5 + 7, sketch001)`
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
await page.waitForTimeout(150)
|
||||
await page.setBodyDimensions({ width: 1200, height: 1200 })
|
||||
await page.setViewportSize({ width: 1200, height: 1200 })
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.updateCamPosition([452, -152, 1166])
|
||||
await u.closeDebugPanel()
|
||||
@ -557,11 +574,11 @@ extrude001 = extrude(5 + 7, sketch001)`
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
const result = makeTemplate`sketch002 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([-12.83, 6.7], %)
|
||||
|> line([${[2.28, 2.35]}, -${0.07}], %)
|
||||
|> line([-3.05, -1.47], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
|> startProfileAt([-12.83, 6.7], %)
|
||||
|> line([${[2.28, 2.35]}, -${0.07}], %)
|
||||
|> line([-3.05, -1.47], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(result.regExp)
|
||||
|
||||
@ -585,6 +602,6 @@ extrude001 = extrude(5 + 7, sketch001)`
|
||||
await page.getByRole('button', { name: 'checkmark Submit command' }).click()
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
@ -1,305 +0,0 @@
|
||||
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>
|
||||
|
||||
// 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
|
||||
)
|
||||
}
|
||||
|
||||
// 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 = function (fn) {
|
||||
return fn(tronApp.dir).then(() => ({
|
||||
dir: tronApp.dir,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// 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,7 +7,6 @@ import { MachinesListing } from 'components/MachineManagerProvider'
|
||||
type EnvFn = (value?: string) => string
|
||||
|
||||
export interface IElectronAPI {
|
||||
resizeWindow: (width: number, height: number) => Promise<void>
|
||||
open: typeof dialog.showOpenDialog
|
||||
save: typeof dialog.showSaveDialog
|
||||
openExternal: typeof shell.openExternal
|
||||
|
||||
29
package.json
@ -81,6 +81,7 @@
|
||||
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver:stop": "kill-port 3000",
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
@ -95,6 +96,8 @@
|
||||
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||
"files:set-notes": "./scripts/set-files-notes.sh",
|
||||
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
||||
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
|
||||
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
|
||||
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
||||
"make:dev": "make dev",
|
||||
@ -103,22 +106,24 @@
|
||||
"tron:package": "electron-forge package",
|
||||
"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'",
|
||||
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron",
|
||||
"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",
|
||||
"test-setup": "yarn install && yarn build:wasm",
|
||||
"test": "vitest --mode development",
|
||||
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
||||
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
||||
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
||||
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
||||
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||
"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:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
||||
"test:playwright:browser:chrome": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron'",
|
||||
"test:playwright:browser:chrome:windows": "playwright test --project=\"Google Chrome\" --config=playwright.ci.config.ts --grep-invert=\"@snapshot|@electron|@skipWin\"",
|
||||
"test:playwright:browser:chrome:ubuntu": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron|@skipLinux'",
|
||||
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep=@electron",
|
||||
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin",
|
||||
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos",
|
||||
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux",
|
||||
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron",
|
||||
"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:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
|
||||
},
|
||||
@ -156,8 +161,8 @@
|
||||
"@electron/rebuild": "^3.6.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.1",
|
||||
"@playwright/test": "^1.49.0",
|
||||
"@nabla/vite-plugin-eslint": "^2.0.5",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
"@types/d3-force": "^3.0.10",
|
||||
@ -169,7 +174,7 @@
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.3.4",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@types/three": "^0.163.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
|
||||
58
playwright.ci.config.ts
Normal file
@ -0,0 +1,58 @@
|
||||
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,
|
||||
},
|
||||
})
|
||||
11
scripts/invalidate-files-bucket.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
base_dir="/releases/modeling-app"
|
||||
if [[ $1 = "--nightly" ]]; then
|
||||
base_dir="/releases/modeling-app/nightly"
|
||||
fi
|
||||
|
||||
echo "Invalidating json and yml files at $base_dir in the download bucket"
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/last_download.json" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-linux-arm64.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-mac.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest.yml" --async
|
||||
@ -200,7 +200,10 @@ function CoreDump() {
|
||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||
[]
|
||||
)
|
||||
useHotkeyWrapper(['mod + shift + .'], () => {
|
||||
// TODO: revisit once progress is made on upstream issue
|
||||
// https://github.com/JohannesKlauss/react-hotkeys-hook/issues/1064
|
||||
// const hotkey = process.platform !== 'linux' ? 'mod + shift + .' : 'mod + shift + >'
|
||||
useHotkeyWrapper(['mod + shift + .', 'mod + shift + >'], () => {
|
||||
toast
|
||||
.promise(
|
||||
coreDump(coreDumpManager, true),
|
||||
|
||||
@ -505,7 +505,8 @@ const ConstraintSymbol = ({
|
||||
constrainInfo: ConstrainInfo
|
||||
verticalPosition: 'top' | 'bottom'
|
||||
}) => {
|
||||
const { context, send } = useModelingContext()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { context } = useModelingContext()
|
||||
const varNameMap: {
|
||||
[key in ConstrainInfo['type']]: {
|
||||
varName: string
|
||||
@ -624,11 +625,18 @@ const ConstraintSymbol = ({
|
||||
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
||||
onClick={toSync(async () => {
|
||||
if (!isConstrained) {
|
||||
send({
|
||||
type: 'Convert to variable',
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
pathToNode,
|
||||
variableName: varName,
|
||||
name: 'Constrain with named value',
|
||||
groupId: 'modeling',
|
||||
argDefaultValues: {
|
||||
currentValue: {
|
||||
pathToNode,
|
||||
variableName: varName,
|
||||
valueText: value,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
} else if (isConstrained) {
|
||||
|
||||
@ -8,11 +8,16 @@ import { getSystemTheme } from 'lib/theme'
|
||||
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
|
||||
import { roundOff } from 'lib/utils'
|
||||
import { varMentions } from 'lib/varCompletionExtension'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import styles from './CommandBarKclInput.module.css'
|
||||
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||
import { useSelector } from '@xstate/react'
|
||||
|
||||
const machineContextSelector = (snapshot?: {
|
||||
context: Record<string, unknown>
|
||||
}) => snapshot?.context
|
||||
|
||||
function CommandBarKclInput({
|
||||
arg,
|
||||
@ -31,12 +36,44 @@ function CommandBarKclInput({
|
||||
arg.name
|
||||
] as KclCommandValue | undefined
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const defaultValue = (arg.defaultValue as string) || ''
|
||||
const argMachineContext = useSelector(
|
||||
arg.machineActor,
|
||||
machineContextSelector
|
||||
)
|
||||
const defaultValue = useMemo(
|
||||
() =>
|
||||
arg.defaultValue
|
||||
? arg.defaultValue instanceof Function
|
||||
? arg.defaultValue(commandBarState.context, argMachineContext)
|
||||
: arg.defaultValue
|
||||
: '',
|
||||
[arg.defaultValue, commandBarState.context, argMachineContext]
|
||||
)
|
||||
const initialVariableName = useMemo(() => {
|
||||
// Use the configured variable name if it exists
|
||||
if (arg.variableName !== undefined) {
|
||||
return arg.variableName instanceof Function
|
||||
? arg.variableName(commandBarState.context, argMachineContext)
|
||||
: arg.variableName
|
||||
}
|
||||
// or derive it from the previously set value or the argument name
|
||||
return previouslySetValue && 'variableName' in previouslySetValue
|
||||
? previouslySetValue.variableName
|
||||
: arg.name
|
||||
}, [
|
||||
arg.variableName,
|
||||
commandBarState.context,
|
||||
argMachineContext,
|
||||
arg.name,
|
||||
previouslySetValue,
|
||||
])
|
||||
const [value, setValue] = useState(
|
||||
previouslySetValue?.valueText || defaultValue || ''
|
||||
)
|
||||
const [createNewVariable, setCreateNewVariable] = useState(
|
||||
previouslySetValue && 'variableName' in previouslySetValue
|
||||
(previouslySetValue && 'variableName' in previouslySetValue) ||
|
||||
arg.createVariableByDefault ||
|
||||
false
|
||||
)
|
||||
const [canSubmit, setCanSubmit] = useState(true)
|
||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
||||
@ -52,10 +89,7 @@ function CommandBarKclInput({
|
||||
isNewVariableNameUnique,
|
||||
} = useCalculateKclExpression({
|
||||
value,
|
||||
initialVariableName:
|
||||
previouslySetValue && 'variableName' in previouslySetValue
|
||||
? previouslySetValue.variableName
|
||||
: arg.name,
|
||||
initialVariableName,
|
||||
})
|
||||
const varMentionData: Completion[] = prevVariables.map((v) => ({
|
||||
label: v.key,
|
||||
|
||||
@ -266,6 +266,7 @@ const FileTreeItem = ({
|
||||
// Let the lsp servers know we closed a file.
|
||||
onFileClose(currentFile?.path || null, project?.path || null)
|
||||
onFileOpen(fileOrDir.path, project?.path || null)
|
||||
kclManager.switchedFiles = true
|
||||
|
||||
// Open kcl files
|
||||
navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { APP_VERSION } from 'routes/Settings'
|
||||
import { APP_VERSION, RELEASE_URL } from 'routes/Settings'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { PATHS } from 'lib/paths'
|
||||
@ -72,10 +72,8 @@ export function LowerRightControls({
|
||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
||||
)}
|
||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
||||
onClick={openExternalBrowserIfDesktop(RELEASE_URL)}
|
||||
href={RELEASE_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}
|
||||
|
||||
@ -41,7 +41,10 @@ import {
|
||||
angleBetweenInfo,
|
||||
applyConstraintAngleBetween,
|
||||
} from './Toolbar/SetAngleBetween'
|
||||
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||
import {
|
||||
applyConstraintAngleLength,
|
||||
applyConstraintLength,
|
||||
} from './Toolbar/setAngleLength'
|
||||
import {
|
||||
canSweepSelection,
|
||||
handleSelectionBatch,
|
||||
@ -51,6 +54,8 @@ import {
|
||||
Selections,
|
||||
updateSelections,
|
||||
canLoftSelection,
|
||||
canRevolveSelection,
|
||||
canShellSelection,
|
||||
} from 'lib/selections'
|
||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||
@ -62,13 +67,15 @@ import {
|
||||
getSketchOrientationDetails,
|
||||
} from 'clientSideScene/sceneEntities'
|
||||
import {
|
||||
moveValueIntoNewVariablePath,
|
||||
insertNamedConstant,
|
||||
replaceValueAtNodePath,
|
||||
sketchOnExtrudedFace,
|
||||
sketchOnOffsetPlane,
|
||||
startSketchOnDefault,
|
||||
} from 'lang/modifyAst'
|
||||
import { Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||
import {
|
||||
doesSceneHaveExtrudedSketch,
|
||||
doesSceneHaveSweepableSketch,
|
||||
getNodePathFromSourceRange,
|
||||
isSingleCursorInPipe,
|
||||
@ -79,7 +86,6 @@ import toast from 'react-hot-toast'
|
||||
import { EditorSelection, Transaction } from '@codemirror/state'
|
||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||
import { getVarNameModal } from 'hooks/useToolbarGuards'
|
||||
import { err, reportRejection, trap } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { modelingMachineEvent } from 'editor/manager'
|
||||
@ -570,6 +576,26 @@ export const ModelingMachineProvider = ({
|
||||
if (err(canSweep)) return false
|
||||
return canSweep
|
||||
},
|
||||
'has valid revolve selection': ({ context: { selectionRanges } }) => {
|
||||
// A user can begin extruding if they either have 1+ faces selected or nothing selected
|
||||
// TODO: I believe this guard only allows for extruding a single face at a time
|
||||
const hasNoSelection =
|
||||
selectionRanges.graphSelections.length === 0 ||
|
||||
isRangeBetweenCharacters(selectionRanges) ||
|
||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||
|
||||
if (hasNoSelection) {
|
||||
// they have no selection, we should enable the button
|
||||
// so they can select the face through the cmdbar
|
||||
// BUT only if there's extrudable geometry
|
||||
return doesSceneHaveSweepableSketch(kclManager.ast)
|
||||
}
|
||||
if (!isSketchPipe(selectionRanges)) return false
|
||||
|
||||
const canSweep = canRevolveSelection(selectionRanges)
|
||||
if (err(canSweep)) return false
|
||||
return canSweep
|
||||
},
|
||||
'has valid loft selection': ({ context: { selectionRanges } }) => {
|
||||
const hasNoSelection =
|
||||
selectionRanges.graphSelections.length === 0 ||
|
||||
@ -585,6 +611,24 @@ export const ModelingMachineProvider = ({
|
||||
if (err(canLoft)) return false
|
||||
return canLoft
|
||||
},
|
||||
'has valid shell selection': ({
|
||||
context: { selectionRanges },
|
||||
event,
|
||||
}) => {
|
||||
const hasNoSelection =
|
||||
selectionRanges.graphSelections.length === 0 ||
|
||||
isRangeBetweenCharacters(selectionRanges) ||
|
||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||
|
||||
if (hasNoSelection) {
|
||||
return doesSceneHaveExtrudedSketch(kclManager.ast)
|
||||
}
|
||||
|
||||
const canShell = canShellSelection(selectionRanges)
|
||||
console.log('canShellSelection', canShellSelection(selectionRanges))
|
||||
if (err(canShell)) return false
|
||||
return canShell
|
||||
},
|
||||
'has valid selection for deletion': ({
|
||||
context: { selectionRanges },
|
||||
}) => {
|
||||
@ -869,12 +913,18 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get length info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
await applyConstraintAngleLength({
|
||||
selectionRanges,
|
||||
})
|
||||
astConstrainLength: fromPromise(
|
||||
async ({
|
||||
input: { selectionRanges, sketchDetails, lengthValue },
|
||||
}) => {
|
||||
if (!lengthValue)
|
||||
return Promise.reject(new Error('No length value'))
|
||||
const constraintResult = await applyConstraintLength({
|
||||
selectionRanges,
|
||||
length: lengthValue,
|
||||
})
|
||||
if (err(constraintResult)) return Promise.reject(constraintResult)
|
||||
const { modifiedAst, pathToNodeMap } = constraintResult
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
@ -1043,38 +1093,88 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get convert to variable info': fromPromise(
|
||||
'Apply named value constraint': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails, data } }) => {
|
||||
if (!sketchDetails)
|
||||
if (!sketchDetails) {
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const { variableName } = await getVarNameModal({
|
||||
valueName: data?.variableName || 'var',
|
||||
})
|
||||
}
|
||||
if (!data) {
|
||||
return Promise.reject(new Error('No data from command flow'))
|
||||
}
|
||||
let pResult = parse(recast(kclManager.ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
let parsed = pResult.program
|
||||
|
||||
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
||||
moveValueIntoNewVariablePath(
|
||||
parsed,
|
||||
kclManager.programMemory,
|
||||
data?.pathToNode || [],
|
||||
variableName
|
||||
let result: {
|
||||
modifiedAst: Node<Program>
|
||||
pathToReplaced: PathToNode | null
|
||||
} = {
|
||||
modifiedAst: parsed,
|
||||
pathToReplaced: null,
|
||||
}
|
||||
// If the user provided a constant name,
|
||||
// we need to insert the named constant
|
||||
// and then replace the node with the constant's name.
|
||||
if ('variableName' in data.namedValue) {
|
||||
const astAfterReplacement = replaceValueAtNodePath({
|
||||
ast: parsed,
|
||||
pathToNode: data.currentValue.pathToNode,
|
||||
newExpressionString: data.namedValue.variableName,
|
||||
})
|
||||
if (trap(astAfterReplacement)) {
|
||||
return Promise.reject(astAfterReplacement)
|
||||
}
|
||||
const parseResultAfterInsertion = parse(
|
||||
recast(
|
||||
insertNamedConstant({
|
||||
node: astAfterReplacement.modifiedAst,
|
||||
newExpression: data.namedValue,
|
||||
})
|
||||
)
|
||||
)
|
||||
pResult = parse(recast(_modifiedAst))
|
||||
if (
|
||||
trap(parseResultAfterInsertion) ||
|
||||
!resultIsOk(parseResultAfterInsertion)
|
||||
)
|
||||
return Promise.reject(parseResultAfterInsertion)
|
||||
result = {
|
||||
modifiedAst: parseResultAfterInsertion.program,
|
||||
pathToReplaced: astAfterReplacement.pathToReplaced,
|
||||
}
|
||||
} else if ('valueText' in data.namedValue) {
|
||||
// If they didn't provide a constant name,
|
||||
// just replace the node with the value.
|
||||
const astAfterReplacement = replaceValueAtNodePath({
|
||||
ast: parsed,
|
||||
pathToNode: data.currentValue.pathToNode,
|
||||
newExpressionString: data.namedValue.valueText,
|
||||
})
|
||||
if (trap(astAfterReplacement)) {
|
||||
return Promise.reject(astAfterReplacement)
|
||||
}
|
||||
// The `replacer` function returns a pathToNode that assumes
|
||||
// an identifier is also being inserted into the AST, creating an off-by-one error.
|
||||
// This corrects that error, but TODO we should fix this upstream
|
||||
// to avoid this kind of error in the future.
|
||||
astAfterReplacement.pathToReplaced[1][0] =
|
||||
(astAfterReplacement.pathToReplaced[1][0] as number) - 1
|
||||
result = astAfterReplacement
|
||||
}
|
||||
|
||||
pResult = parse(recast(result.modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
parsed = pResult.program
|
||||
|
||||
if (trap(parsed)) return Promise.reject(parsed)
|
||||
parsed = parsed as Node<Program>
|
||||
if (!pathToReplacedNode)
|
||||
if (!result.pathToReplaced)
|
||||
return Promise.reject(new Error('No path to replaced node'))
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
pathToReplacedNode || [],
|
||||
result.pathToReplaced || [],
|
||||
parsed,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -1087,7 +1187,7 @@ export const ModelingMachineProvider = ({
|
||||
)
|
||||
|
||||
const selection = updateSelections(
|
||||
{ 0: pathToReplacedNode },
|
||||
{ 0: result.pathToReplaced },
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
@ -1095,7 +1195,7 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode: pathToReplacedNode,
|
||||
updatedPathToNode: result.pathToReplaced,
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
@ -76,7 +76,7 @@ export const ModelingPane = ({
|
||||
return (
|
||||
<section
|
||||
{...props}
|
||||
title={title && typeof title === 'string' ? title : ''}
|
||||
aria-label={title && typeof title === 'string' ? title : ''}
|
||||
data-testid={detailsTestId}
|
||||
id={id}
|
||||
className={
|
||||
|
||||
@ -40,7 +40,9 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={() => kclManager.format()}
|
||||
onClick={() => {
|
||||
kclManager.format().catch(reportRejection)
|
||||
}}
|
||||
className={styles.button}
|
||||
>
|
||||
<span>Format code</span>
|
||||
|
||||
@ -10,7 +10,7 @@ import { APP_NAME } from 'lib/constants'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { useLspContext } from './LspProvider'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
@ -68,8 +68,7 @@ function AppLogoLink({
|
||||
data-testid="app-logo"
|
||||
onClick={() => {
|
||||
onProjectClose(file || null, project?.path || null, false)
|
||||
// Clear the scene.
|
||||
engineCommandManager.clearScene()
|
||||
kclManager.switchedFiles = true
|
||||
}}
|
||||
to={PATHS.HOME}
|
||||
className={wrapperClassName + ' hover:before:brightness-110'}
|
||||
@ -190,8 +189,7 @@ function ProjectMenuPopover({
|
||||
className: !isDesktop() ? 'hidden' : '',
|
||||
onClick: () => {
|
||||
onProjectClose(file || null, project?.path || null, true)
|
||||
// Clear the scene.
|
||||
engineCommandManager.clearScene()
|
||||
kclManager.switchedFiles = true
|
||||
},
|
||||
},
|
||||
].filter(
|
||||
|
||||
@ -10,7 +10,7 @@ interface AllKeybindingsFieldsProps {}
|
||||
|
||||
export const AllKeybindingsFields = forwardRef(
|
||||
(
|
||||
props: AllKeybindingsFieldsProps,
|
||||
_props: AllKeybindingsFieldsProps,
|
||||
scrollRef: ForwardedRef<HTMLDivElement>
|
||||
) => {
|
||||
// This is how we will get the interaction map from the context
|
||||
@ -25,7 +25,7 @@ export const AllKeybindingsFields = forwardRef(
|
||||
.map(([category, categoryItems]) => (
|
||||
<div className="flex flex-col gap-4 px-2 pr-4">
|
||||
<h2
|
||||
id={`category-${category}`}
|
||||
id={`category-${category.replaceAll(/\s/g, '-')}`}
|
||||
className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold"
|
||||
>
|
||||
{category}
|
||||
|
||||
@ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { SettingsFieldInput } from './SettingsFieldInput'
|
||||
import toast from 'react-hot-toast'
|
||||
import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings'
|
||||
import { APP_VERSION, IS_NIGHTLY, RELEASE_URL } from 'routes/Settings'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import {
|
||||
createAndOpenNewTutorialProject,
|
||||
@ -246,10 +246,8 @@ export const AllSettingsFields = forwardRef(
|
||||
to inject the version from package.json */}
|
||||
App version {APP_VERSION}.{' '}
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
||||
)}
|
||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
||||
onClick={openExternalBrowserIfDesktop(RELEASE_URL)}
|
||||
href={RELEASE_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@ -271,7 +269,7 @@ export const AllSettingsFields = forwardRef(
|
||||
, and start a discussion if you don't see it! Your feedback will
|
||||
help us prioritize what to build next.
|
||||
</p>
|
||||
{PACKAGE_NAME.indexOf('-nightly') === -1 && (
|
||||
{!IS_NIGHTLY && (
|
||||
<p className="max-w-2xl mt-6">
|
||||
Want to experience the latest and (hopefully) greatest from our
|
||||
main development branch?{' '}
|
||||
|
||||
@ -19,7 +19,7 @@ export function KeybindingsSectionsList({
|
||||
key={category}
|
||||
onClick={() =>
|
||||
scrollRef.current
|
||||
?.querySelector(`#category-${category}`)
|
||||
?.querySelector(`#category-${category.replaceAll(/\s/g, '-')}`)
|
||||
?.scrollIntoView({
|
||||
block: 'center',
|
||||
behavior: 'smooth',
|
||||
|
||||
@ -22,6 +22,7 @@ import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { normaliseAngle } from '../../lib/utils'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
import { KclCommandValue } from 'lib/commandTypes'
|
||||
|
||||
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
||||
|
||||
@ -63,6 +64,57 @@ export function angleLengthInfo({
|
||||
return { enabled, transforms }
|
||||
}
|
||||
|
||||
export async function applyConstraintLength({
|
||||
length,
|
||||
selectionRanges,
|
||||
}: {
|
||||
length: KclCommandValue
|
||||
selectionRanges: Selections
|
||||
}) {
|
||||
const ast = kclManager.ast
|
||||
const angleLength = angleLengthInfo({ selectionRanges })
|
||||
if (err(angleLength)) return angleLength
|
||||
const { transforms } = angleLength
|
||||
|
||||
let distanceExpression: Expr = length.valueAst
|
||||
|
||||
/**
|
||||
* To be "constrained", the value must be a binary expression, a named value, or a function call.
|
||||
* If it has a variable name, we need to insert a variable declaration at the correct index.
|
||||
*/
|
||||
if (
|
||||
'variableName' in length &&
|
||||
length.variableName &&
|
||||
length.insertIndex !== undefined
|
||||
) {
|
||||
const newBody = [...ast.body]
|
||||
newBody.splice(length.insertIndex, 0, length.variableDeclarationAst)
|
||||
ast.body = newBody
|
||||
distanceExpression = createIdentifier(length.variableName)
|
||||
}
|
||||
|
||||
if (!isExprBinaryPart(distanceExpression)) {
|
||||
return new Error('Invalid valueNode, is not a BinaryPart')
|
||||
}
|
||||
|
||||
const retval = transformAstSketchLines({
|
||||
ast,
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
referenceSegName: '',
|
||||
forceValueUsedInTransform: distanceExpression,
|
||||
})
|
||||
if (err(retval)) return Promise.reject(retval)
|
||||
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = retval
|
||||
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap,
|
||||
}
|
||||
}
|
||||
|
||||
export async function applyConstraintAngleLength({
|
||||
selectionRanges,
|
||||
angleOrLength = 'setLength',
|
||||
|
||||
@ -24,6 +24,8 @@ export function useConvertToVariable(range?: SourceRange) {
|
||||
}, [enable])
|
||||
|
||||
useEffect(() => {
|
||||
// Return early if there are no selection ranges for whatever reason
|
||||
if (!context.selectionRanges) return
|
||||
const parsed = ast
|
||||
|
||||
const meta = isNodeSafeToReplace(
|
||||
|
||||
@ -12,6 +12,7 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
|
||||
|
||||
import {
|
||||
CallExpression,
|
||||
clearSceneAndBustCache,
|
||||
emptyExecState,
|
||||
ExecState,
|
||||
initPromise,
|
||||
@ -60,6 +61,7 @@ export class KclManager {
|
||||
private _executeIsStale: ExecuteArgs | null = null
|
||||
private _wasmInitFailed = true
|
||||
private _hasErrors = false
|
||||
private _switchedFiles = false
|
||||
|
||||
engineCommandManager: EngineCommandManager
|
||||
|
||||
@ -79,6 +81,10 @@ export class KclManager {
|
||||
this._astCallBack(ast)
|
||||
}
|
||||
|
||||
set switchedFiles(switchedFiles: boolean) {
|
||||
this._switchedFiles = switchedFiles
|
||||
}
|
||||
|
||||
get programMemory() {
|
||||
return this._programMemory
|
||||
}
|
||||
@ -166,8 +172,12 @@ export class KclManager {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.ensureWasmInit().then(() => {
|
||||
this.ast = this.safeParse(codeManager.code) || this.ast
|
||||
this.ensureWasmInit().then(async () => {
|
||||
await this.safeParse(codeManager.code).then((ast) => {
|
||||
if (ast) {
|
||||
this.ast = ast
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -211,7 +221,25 @@ export class KclManager {
|
||||
}
|
||||
}
|
||||
|
||||
safeParse(code: string): Node<Program> | null {
|
||||
// (jess) I'm not in love with this, but it ensures we clear the scene and
|
||||
// bust the cache on
|
||||
// errors from parsing when opening new files.
|
||||
// Why not just clear the cache on all parse errors, you ask? well its actually
|
||||
// really nice to keep the cache on parse errors within the same file, and
|
||||
// only bust on engine errors esp if they take a long time to execute and
|
||||
// you hit the wrong key!
|
||||
private async checkIfSwitchedFilesShouldClear() {
|
||||
// If we were switching files and we hit an error on parse we need to bust
|
||||
// the cache and clear the scene.
|
||||
if (this._hasErrors && this._switchedFiles) {
|
||||
await clearSceneAndBustCache(this.engineCommandManager)
|
||||
} else if (this._switchedFiles) {
|
||||
// Reset the switched files boolean.
|
||||
this._switchedFiles = false
|
||||
}
|
||||
}
|
||||
|
||||
async safeParse(code: string): Promise<Node<Program> | null> {
|
||||
const result = parse(code)
|
||||
this.diagnostics = []
|
||||
this._hasErrors = false
|
||||
@ -220,6 +248,8 @@ export class KclManager {
|
||||
const kclerror: KCLError = result as KCLError
|
||||
this.diagnostics = kclErrorsToDiagnostics([kclerror])
|
||||
this._hasErrors = true
|
||||
|
||||
await this.checkIfSwitchedFilesShouldClear()
|
||||
return null
|
||||
}
|
||||
|
||||
@ -228,6 +258,7 @@ export class KclManager {
|
||||
if (result.errors.length > 0) {
|
||||
this._hasErrors = true
|
||||
|
||||
await this.checkIfSwitchedFilesShouldClear()
|
||||
return null
|
||||
}
|
||||
|
||||
@ -353,7 +384,7 @@ export class KclManager {
|
||||
console.error(newCode)
|
||||
return
|
||||
}
|
||||
const newAst = this.safeParse(newCode)
|
||||
const newAst = await this.safeParse(newCode)
|
||||
if (!newAst) {
|
||||
this.clearAst()
|
||||
return
|
||||
@ -408,7 +439,7 @@ export class KclManager {
|
||||
})
|
||||
}
|
||||
async executeCode(zoomToFit?: boolean): Promise<void> {
|
||||
const ast = this.safeParse(codeManager.code)
|
||||
const ast = await this.safeParse(codeManager.code)
|
||||
if (!ast) {
|
||||
this.clearAst()
|
||||
return
|
||||
@ -416,9 +447,9 @@ export class KclManager {
|
||||
this.ast = { ...ast }
|
||||
return this.executeAst({ zoomToFit })
|
||||
}
|
||||
format() {
|
||||
async format() {
|
||||
const originalCode = codeManager.code
|
||||
const ast = this.safeParse(originalCode)
|
||||
const ast = await this.safeParse(originalCode)
|
||||
if (!ast) {
|
||||
this.clearAst()
|
||||
return
|
||||
@ -458,7 +489,7 @@ export class KclManager {
|
||||
const newCode = recast(ast)
|
||||
if (err(newCode)) return Promise.reject(newCode)
|
||||
|
||||
const astWithUpdatedSource = this.safeParse(newCode)
|
||||
const astWithUpdatedSource = await this.safeParse(newCode)
|
||||
if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast'))
|
||||
let returnVal: Selections | undefined = undefined
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ export default class CodeManager {
|
||||
} else if (storedCode === null) {
|
||||
this.code = bracket
|
||||
} else {
|
||||
this.code = storedCode || ''
|
||||
this.code = storedCode
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,10 +57,6 @@ export default class CodeManager {
|
||||
return this._code
|
||||
}
|
||||
|
||||
localStoragePersistCode(): string {
|
||||
return safeLSGetItem(PERSIST_CODE_KEY) || ''
|
||||
}
|
||||
|
||||
registerCallBacks({ setCode }: { setCode: (arg: string) => void }) {
|
||||
this.#updateState = setCode
|
||||
}
|
||||
@ -169,7 +165,7 @@ export default class CodeManager {
|
||||
}
|
||||
|
||||
function safeLSGetItem(key: string) {
|
||||
if (typeof window === 'undefined') return
|
||||
if (typeof window === 'undefined') return null
|
||||
return localStorage?.getItem(key)
|
||||
}
|
||||
|
||||
|
||||
@ -45,6 +45,7 @@ import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
||||
|
||||
export function startSketchOnDefault(
|
||||
node: Node<Program>,
|
||||
@ -590,6 +591,25 @@ export function addOffsetPlane({
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a modified clone of an AST with a named constant inserted into the body
|
||||
*/
|
||||
export function insertNamedConstant({
|
||||
node,
|
||||
newExpression,
|
||||
}: {
|
||||
node: Node<Program>
|
||||
newExpression: KclExpressionWithVariable
|
||||
}): Node<Program> {
|
||||
const ast = structuredClone(node)
|
||||
ast.body.splice(
|
||||
newExpression.insertIndex,
|
||||
0,
|
||||
newExpression.variableDeclarationAst
|
||||
)
|
||||
return ast
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the AST to create a new sketch using the variable declaration
|
||||
* of an offset plane. The new sketch just has to come after the offset
|
||||
@ -933,6 +953,31 @@ export function giveSketchFnCallTag(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a
|
||||
*/
|
||||
export function replaceValueAtNodePath({
|
||||
ast,
|
||||
pathToNode,
|
||||
newExpressionString,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
pathToNode: PathToNode
|
||||
newExpressionString: string
|
||||
}) {
|
||||
const replaceCheckResult = isNodeSafeToReplacePath(ast, pathToNode)
|
||||
if (err(replaceCheckResult)) {
|
||||
return replaceCheckResult
|
||||
}
|
||||
const { isSafe, value, replacer } = replaceCheckResult
|
||||
|
||||
if (!isSafe || value.type === 'Identifier') {
|
||||
return new Error('Not safe to replace')
|
||||
}
|
||||
|
||||
return replacer(ast, newExpressionString)
|
||||
}
|
||||
|
||||
export function moveValueIntoNewVariablePath(
|
||||
ast: Node<Program>,
|
||||
programMemory: ProgramMemory,
|
||||
|
||||
@ -22,7 +22,7 @@ import {
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||
import { createLiteral } from 'lang/modifyAst'
|
||||
import { err } from 'lib/trap'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { VITE_KC_DEV_TOKEN } from 'env'
|
||||
import { isOverlap } from 'lib/utils'
|
||||
@ -118,13 +118,8 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
const selection: Selections = {
|
||||
graphSelections: [
|
||||
{
|
||||
codeRef: codeRefFromRange(segmentRange, ast),
|
||||
},
|
||||
],
|
||||
otherSelections: [],
|
||||
const selection: Selection = {
|
||||
codeRef: codeRefFromRange(segmentRange, ast),
|
||||
}
|
||||
|
||||
// executeAst and artifactGraph
|
||||
|
||||
@ -29,7 +29,7 @@ import {
|
||||
sketchLineHelperMap,
|
||||
} from '../std/sketch'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import { KclCommandValue } from 'lib/commandTypes'
|
||||
import {
|
||||
Artifact,
|
||||
@ -99,14 +99,9 @@ export function modifyAstWithEdgeTreatmentAndTag(
|
||||
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
|
||||
|
||||
for (const selection of selections.graphSelections) {
|
||||
const singleSelection = {
|
||||
graphSelections: [selection],
|
||||
otherSelections: [],
|
||||
}
|
||||
|
||||
const result = getPathToExtrudeForSegmentSelection(
|
||||
clonedAstForGetExtrude,
|
||||
singleSelection,
|
||||
selection,
|
||||
artifactGraph
|
||||
)
|
||||
if (err(result)) return result
|
||||
@ -259,12 +254,12 @@ function insertParametersIntoAst(
|
||||
|
||||
export function getPathToExtrudeForSegmentSelection(
|
||||
ast: Program,
|
||||
selection: Selections,
|
||||
selection: Selection,
|
||||
artifactGraph: ArtifactGraph
|
||||
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
||||
const pathToSegmentNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
selection.graphSelections[0]?.codeRef?.range
|
||||
selection.codeRef?.range
|
||||
)
|
||||
|
||||
const varDecNode = getNodeFromPath<VariableDeclaration>(
|
||||
@ -308,7 +303,7 @@ async function updateAstAndFocus(
|
||||
}
|
||||
}
|
||||
|
||||
function mutateAstWithTagForSketchSegment(
|
||||
export function mutateAstWithTagForSketchSegment(
|
||||
astClone: Node<Program>,
|
||||
pathToSegmentNode: PathToNode
|
||||
): { modifiedAst: Program; tag: string } | Error {
|
||||
@ -340,7 +335,7 @@ function mutateAstWithTagForSketchSegment(
|
||||
return { modifiedAst: astClone, tag }
|
||||
}
|
||||
|
||||
function getEdgeTagCall(
|
||||
export function getEdgeTagCall(
|
||||
tag: string,
|
||||
artifact: Artifact
|
||||
): Node<Identifier | CallExpression> {
|
||||
|
||||
154
src/lang/modifyAst/addRevolve.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import { err } from 'lib/trap'
|
||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||
import {
|
||||
Program,
|
||||
PathToNode,
|
||||
Expr,
|
||||
CallExpression,
|
||||
PipeExpression,
|
||||
VariableDeclarator,
|
||||
} from 'lang/wasm'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import {
|
||||
createLiteral,
|
||||
createCallExpressionStdLib,
|
||||
createObjectExpression,
|
||||
createIdentifier,
|
||||
createPipeExpression,
|
||||
findUniqueName,
|
||||
createVariableDeclaration,
|
||||
} from 'lang/modifyAst'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import {
|
||||
mutateAstWithTagForSketchSegment,
|
||||
getEdgeTagCall,
|
||||
} from 'lang/modifyAst/addEdgeTreatment'
|
||||
export function revolveSketch(
|
||||
ast: Node<Program>,
|
||||
pathToSketchNode: PathToNode,
|
||||
shouldPipe = false,
|
||||
angle: Expr = createLiteral(4),
|
||||
axis: Selections
|
||||
):
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
pathToSketchNode: PathToNode
|
||||
pathToRevolveArg: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const clonedAst = structuredClone(ast)
|
||||
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
|
||||
if (err(sketchNode)) return sketchNode
|
||||
|
||||
// testing code
|
||||
const pathToAxisSelection = getNodePathFromSourceRange(
|
||||
clonedAst,
|
||||
axis.graphSelections[0]?.codeRef.range
|
||||
)
|
||||
|
||||
const lineNode = getNodeFromPath<CallExpression>(
|
||||
clonedAst,
|
||||
pathToAxisSelection,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(lineNode)) return lineNode
|
||||
|
||||
// TODO Kevin: What if |> close(%)?
|
||||
// TODO Kevin: What if opposite edge
|
||||
// TODO Kevin: What if the edge isn't planar to the sketch?
|
||||
// TODO Kevin: add a tag.
|
||||
const tagResult = mutateAstWithTagForSketchSegment(
|
||||
clonedAst,
|
||||
pathToAxisSelection
|
||||
)
|
||||
|
||||
// Have the tag whether it is already created or a new one is generated
|
||||
if (err(tagResult)) return tagResult
|
||||
const { tag } = tagResult
|
||||
|
||||
/* Original Code */
|
||||
const { node: sketchExpression } = sketchNode
|
||||
|
||||
// determine if sketchExpression is in a pipeExpression or not
|
||||
const sketchPipeExpressionNode = getNodeFromPath<PipeExpression>(
|
||||
clonedAst,
|
||||
pathToSketchNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(sketchPipeExpressionNode)) return sketchPipeExpressionNode
|
||||
const { node: sketchPipeExpression } = sketchPipeExpressionNode
|
||||
const isInPipeExpression = sketchPipeExpression.type === 'PipeExpression'
|
||||
|
||||
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
|
||||
clonedAst,
|
||||
pathToSketchNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
|
||||
const {
|
||||
node: sketchVariableDeclarator,
|
||||
shallowPath: sketchPathToDecleration,
|
||||
} = sketchVariableDeclaratorNode
|
||||
|
||||
const axisSelection = axis?.graphSelections[0]?.artifact
|
||||
|
||||
if (!axisSelection) return new Error('Axis selection is missing.')
|
||||
|
||||
const revolveCall = createCallExpressionStdLib('revolve', [
|
||||
createObjectExpression({
|
||||
angle: angle,
|
||||
axis: getEdgeTagCall(tag, axisSelection),
|
||||
}),
|
||||
createIdentifier(sketchVariableDeclarator.id.name),
|
||||
])
|
||||
|
||||
if (shouldPipe) {
|
||||
const pipeChain = createPipeExpression(
|
||||
isInPipeExpression
|
||||
? [...sketchPipeExpression.body, revolveCall]
|
||||
: [sketchExpression as any, revolveCall]
|
||||
)
|
||||
|
||||
sketchVariableDeclarator.init = pipeChain
|
||||
const pathToRevolveArg: PathToNode = [
|
||||
...sketchPathToDecleration,
|
||||
['init', 'VariableDeclarator'],
|
||||
['body', ''],
|
||||
[pipeChain.body.length - 1, 'index'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
|
||||
return {
|
||||
modifiedAst: clonedAst,
|
||||
pathToSketchNode,
|
||||
pathToRevolveArg,
|
||||
}
|
||||
}
|
||||
|
||||
// We're not creating a pipe expression,
|
||||
// but rather a separate constant for the extrusion
|
||||
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
|
||||
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
|
||||
const sketchIndexInPathToNode =
|
||||
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
||||
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
|
||||
if (typeof sketchIndexInBody !== 'number')
|
||||
return new Error('expected sketchIndexInBody to be a number')
|
||||
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
||||
|
||||
const pathToRevolveArg: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndexInBody + 1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
return {
|
||||
modifiedAst: clonedAst,
|
||||
pathToSketchNode: [...pathToSketchNode.slice(0, -1), [-1, 'index']],
|
||||
pathToRevolveArg,
|
||||
}
|
||||
}
|
||||
123
src/lang/modifyAst/addShell.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { ArtifactGraph } from 'lang/std/artifactGraph'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Expr } from 'wasm-lib/kcl/bindings/Expr'
|
||||
import { Program } from 'wasm-lib/kcl/bindings/Program'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { PathToNode, VariableDeclarator } from 'lang/wasm'
|
||||
import {
|
||||
getPathToExtrudeForSegmentSelection,
|
||||
mutateAstWithTagForSketchSegment,
|
||||
} from './addEdgeTreatment'
|
||||
import { getNodeFromPath } from 'lang/queryAst'
|
||||
import { err } from 'lib/trap'
|
||||
import {
|
||||
createLiteral,
|
||||
createIdentifier,
|
||||
findUniqueName,
|
||||
createCallExpressionStdLib,
|
||||
createObjectExpression,
|
||||
createArrayExpression,
|
||||
createVariableDeclaration,
|
||||
} from 'lang/modifyAst'
|
||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||
|
||||
export function addShell({
|
||||
node,
|
||||
selection,
|
||||
artifactGraph,
|
||||
thickness,
|
||||
}: {
|
||||
node: Node<Program>
|
||||
selection: Selections
|
||||
artifactGraph: ArtifactGraph
|
||||
thickness: Expr
|
||||
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
|
||||
const modifiedAst = structuredClone(node)
|
||||
|
||||
// Look up the corresponding extrude
|
||||
const clonedAstForGetExtrude = structuredClone(modifiedAst)
|
||||
|
||||
const expressions: Expr[] = []
|
||||
let pathToExtrudeNode: PathToNode | undefined = undefined
|
||||
for (const graphSelection of selection.graphSelections) {
|
||||
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
|
||||
clonedAstForGetExtrude,
|
||||
graphSelection,
|
||||
artifactGraph
|
||||
)
|
||||
if (err(extrudeLookupResult)) {
|
||||
return new Error("Couldn't find extrude")
|
||||
}
|
||||
|
||||
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
|
||||
// Get the sketch ref from the selection
|
||||
// TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between.
|
||||
// We must find a technique for these situations that is robust to intermediate declarations
|
||||
const sketchNode = getNodeFromPath<VariableDeclarator>(
|
||||
modifiedAst,
|
||||
graphSelection.codeRef.pathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(sketchNode)) {
|
||||
return sketchNode
|
||||
}
|
||||
|
||||
const selectedArtifact = graphSelection.artifact
|
||||
if (!selectedArtifact) {
|
||||
return new Error('Bad artifact')
|
||||
}
|
||||
|
||||
// Check on the selection, and handle the wall vs cap casees
|
||||
let expr: Expr
|
||||
if (selectedArtifact.type === 'cap') {
|
||||
expr = createLiteral(selectedArtifact.subType)
|
||||
} else if (selectedArtifact.type === 'wall') {
|
||||
const tagResult = mutateAstWithTagForSketchSegment(
|
||||
modifiedAst,
|
||||
extrudeLookupResult.pathToSegmentNode
|
||||
)
|
||||
if (err(tagResult)) return tagResult
|
||||
const { tag } = tagResult
|
||||
expr = createIdentifier(tag)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
expressions.push(expr)
|
||||
}
|
||||
|
||||
if (!pathToExtrudeNode) return new Error('No extrude found')
|
||||
|
||||
const extrudeNode = getNodeFromPath<VariableDeclarator>(
|
||||
modifiedAst,
|
||||
pathToExtrudeNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(extrudeNode)) {
|
||||
return extrudeNode
|
||||
}
|
||||
|
||||
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SHELL)
|
||||
const shell = createCallExpressionStdLib('shell', [
|
||||
createObjectExpression({
|
||||
faces: createArrayExpression(expressions),
|
||||
thickness,
|
||||
}),
|
||||
createIdentifier(extrudeNode.node.id.name),
|
||||
])
|
||||
const declaration = createVariableDeclaration(name, shell)
|
||||
|
||||
// TODO: check if we should append at the end like here or right after the extrude
|
||||
modifiedAst.body.push(declaration)
|
||||
const pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[modifiedAst.body.length - 1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNode,
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@ import {
|
||||
doesSceneHaveSweepableSketch,
|
||||
traverse,
|
||||
getNodeFromPath,
|
||||
doesSceneHaveExtrudedSketch,
|
||||
} from './queryAst'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import {
|
||||
@ -654,6 +655,38 @@ extrude001 = extrude(10, sketch001)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Testing doesSceneHaveExtrudedSketch', () => {
|
||||
it('finds extruded sketch as variable', async () => {
|
||||
const exampleCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||
extrude001 = extrude(1, sketch001)
|
||||
`
|
||||
const ast = assertParse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = doesSceneHaveExtrudedSketch(ast)
|
||||
expect(extrudable).toBeTruthy()
|
||||
})
|
||||
it('finds extruded sketch in pipe', async () => {
|
||||
const exampleCode = `extrude001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||
|> extrude(1, %)
|
||||
`
|
||||
const ast = assertParse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = doesSceneHaveExtrudedSketch(ast)
|
||||
expect(extrudable).toBeTruthy()
|
||||
})
|
||||
it('finds no extrusion with sketch only', async () => {
|
||||
const exampleCode = `extrude001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||
`
|
||||
const ast = assertParse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = doesSceneHaveExtrudedSketch(ast)
|
||||
expect(extrudable).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Testing traverse and pathToNode', () => {
|
||||
it.each([
|
||||
['basic', '2.73'],
|
||||
|
||||
@ -1064,6 +1064,35 @@ export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
|
||||
return Object.keys(theMap).length >= count
|
||||
}
|
||||
|
||||
export function doesSceneHaveExtrudedSketch(ast: Node<Program>) {
|
||||
const theMap: any = {}
|
||||
traverse(ast as any, {
|
||||
enter(node) {
|
||||
if (
|
||||
node.type === 'VariableDeclarator' &&
|
||||
node.init?.type === 'PipeExpression'
|
||||
) {
|
||||
for (const pipe of node.init.body) {
|
||||
if (
|
||||
pipe.type === 'CallExpression' &&
|
||||
pipe.callee.name === 'extrude'
|
||||
) {
|
||||
theMap[node.id.name] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee.name === 'extrude' &&
|
||||
node.arguments[1]?.type === 'Identifier'
|
||||
) {
|
||||
theMap[node.moduleId] = true
|
||||
}
|
||||
},
|
||||
})
|
||||
return Object.keys(theMap).length > 0
|
||||
}
|
||||
|
||||
export function getObjExprProperty(
|
||||
node: ObjectExpression,
|
||||
propName: string
|
||||
|
||||
@ -871,3 +871,15 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
|
||||
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
|
||||
}
|
||||
|
||||
@ -1879,17 +1879,6 @@ export class EngineCommandManager extends EventTarget {
|
||||
}
|
||||
return JSON.stringify(this.defaultPlanes)
|
||||
}
|
||||
clearScene(): void {
|
||||
const deleteCmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'scene_clear_all',
|
||||
},
|
||||
}
|
||||
this.clearDefaultPlanes()
|
||||
this.engineConnection?.send(deleteCmd)
|
||||
}
|
||||
addCommandLog(message: CommandLog) {
|
||||
if (this.commandLogs.length > 500) {
|
||||
this.commandLogs.shift()
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import { err } from 'lib/trap'
|
||||
import { parse, ParseResult } from './wasm'
|
||||
import { initPromise, parse, ParseResult } from './wasm'
|
||||
import { enginelessExecutor } from 'lib/testHelpers'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
|
||||
beforeEach(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
it('can execute parsed AST', async () => {
|
||||
const code = `x = 1
|
||||
// A comment.`
|
||||
|
||||
@ -16,6 +16,7 @@ import init, {
|
||||
parse_project_settings,
|
||||
default_project_settings,
|
||||
base64_decode,
|
||||
clear_scene_and_bust_cache,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
import { KCLError } from './errors'
|
||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||
@ -698,6 +699,21 @@ export function defaultAppSettings(): DeepPartial<Configuration> | Error {
|
||||
return default_app_settings()
|
||||
}
|
||||
|
||||
export async function clearSceneAndBustCache(
|
||||
engineCommandManager: EngineCommandManager
|
||||
): Promise<null | Error> {
|
||||
try {
|
||||
await clear_scene_and_bust_cache(engineCommandManager)
|
||||
} catch (e: any) {
|
||||
console.error('clear_scene_and_bust_cache: error', e)
|
||||
return Promise.reject(
|
||||
new Error(`Error on clear_scene_and_bust_cache: ${e}`)
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function parseAppSettings(
|
||||
toml: string
|
||||
): DeepPartial<Configuration> | Error {
|
||||
|
||||
@ -32,8 +32,14 @@ export function mouseControlsToCameraSystem(
|
||||
mouseControl: MouseControlType | undefined
|
||||
): CameraSystem | undefined {
|
||||
switch (mouseControl) {
|
||||
// TODO: understand why the values come back without underscores and fix the root cause
|
||||
// @ts-ignore: TS2678
|
||||
case 'kittycad':
|
||||
case 'kitty_cad':
|
||||
return 'KittyCAD'
|
||||
// TODO: understand why the values come back without underscores and fix the root cause
|
||||
// @ts-ignore: TS2678
|
||||
case 'onshape':
|
||||
case 'on_shape':
|
||||
return 'OnShape'
|
||||
case 'trackpad_friendly':
|
||||
@ -44,6 +50,9 @@ export function mouseControlsToCameraSystem(
|
||||
return 'NX'
|
||||
case 'creo':
|
||||
return 'Creo'
|
||||
// TODO: understand why the values come back without underscores and fix the root cause
|
||||
// @ts-ignore: TS2678
|
||||
case 'autocad':
|
||||
case 'auto_cad':
|
||||
return 'AutoCAD'
|
||||
default:
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { angleLengthInfo } from 'components/Toolbar/setAngleLength'
|
||||
import { transformAstSketchLines } from 'lang/std/sketchcombos'
|
||||
import { PathToNode } from 'lang/wasm'
|
||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||
import { revolveAxisValidator } from './validators'
|
||||
|
||||
type OutputFormat = Models['OutputFormat_type']
|
||||
type OutputTypeKey = OutputFormat['type']
|
||||
@ -34,9 +40,14 @@ export type ModelingCommandSchema = {
|
||||
Loft: {
|
||||
selection: Selections
|
||||
}
|
||||
Shell: {
|
||||
selection: Selections
|
||||
thickness: KclCommandValue
|
||||
}
|
||||
Revolve: {
|
||||
selection: Selections
|
||||
angle: KclCommandValue
|
||||
axis: Selections
|
||||
}
|
||||
Fillet: {
|
||||
// todo
|
||||
@ -50,6 +61,18 @@ export type ModelingCommandSchema = {
|
||||
'change tool': {
|
||||
tool: SketchTool
|
||||
}
|
||||
'Constrain length': {
|
||||
selection: Selections
|
||||
length: KclCommandValue
|
||||
}
|
||||
'Constrain with named value': {
|
||||
currentValue: {
|
||||
valueText: string
|
||||
pathToNode: PathToNode
|
||||
variableName: string
|
||||
}
|
||||
namedValue: KclCommandValue
|
||||
}
|
||||
'Text-to-CAD': {
|
||||
prompt: string
|
||||
}
|
||||
@ -277,6 +300,25 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
Shell: {
|
||||
description: 'Hollow out a 3D solid.',
|
||||
icon: 'shell',
|
||||
needsReview: true,
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['cap', 'wall'],
|
||||
multiple: true,
|
||||
required: true,
|
||||
skip: false,
|
||||
},
|
||||
thickness: {
|
||||
inputType: 'kcl',
|
||||
defaultValue: KCL_DEFAULT_LENGTH,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
|
||||
Revolve: {
|
||||
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
||||
@ -290,6 +332,13 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
required: true,
|
||||
skip: true,
|
||||
},
|
||||
axis: {
|
||||
required: true,
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
||||
multiple: false,
|
||||
validation: revolveAxisValidator,
|
||||
},
|
||||
angle: {
|
||||
inputType: 'kcl',
|
||||
defaultValue: KCL_DEFAULT_DEGREE,
|
||||
@ -337,6 +386,88 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
'Constrain length': {
|
||||
description: 'Constrain the length of one or more segments.',
|
||||
icon: 'dimension',
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['segment'],
|
||||
multiple: false,
|
||||
required: true,
|
||||
skip: true,
|
||||
},
|
||||
length: {
|
||||
inputType: 'kcl',
|
||||
required: true,
|
||||
createVariableByDefault: true,
|
||||
defaultValue(_, machineContext) {
|
||||
const selectionRanges = machineContext?.selectionRanges
|
||||
if (!selectionRanges) return KCL_DEFAULT_LENGTH
|
||||
const angleLength = angleLengthInfo({
|
||||
selectionRanges,
|
||||
angleOrLength: 'setLength',
|
||||
})
|
||||
if (err(angleLength)) return KCL_DEFAULT_LENGTH
|
||||
const { transforms } = angleLength
|
||||
|
||||
// QUESTION: is it okay to reference kclManager here? will its state be up to date?
|
||||
const sketched = transformAstSketchLines({
|
||||
ast: structuredClone(kclManager.ast),
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(sketched)) return KCL_DEFAULT_LENGTH
|
||||
const { valueUsedInTransform } = sketched
|
||||
return valueUsedInTransform?.toString() || KCL_DEFAULT_LENGTH
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'Constrain with named value': {
|
||||
description: 'Constrain a value by making it a named constant.',
|
||||
icon: 'make-variable',
|
||||
args: {
|
||||
currentValue: {
|
||||
description:
|
||||
'Path to the node in the AST to constrain. This is never shown to the user.',
|
||||
inputType: 'text',
|
||||
required: false,
|
||||
skip: true,
|
||||
},
|
||||
namedValue: {
|
||||
inputType: 'kcl',
|
||||
required: true,
|
||||
createVariableByDefault: true,
|
||||
variableName(commandBarContext, machineContext) {
|
||||
const { currentValue } = commandBarContext.argumentsToSubmit
|
||||
if (
|
||||
!currentValue ||
|
||||
!(currentValue instanceof Object) ||
|
||||
!('variableName' in currentValue) ||
|
||||
typeof currentValue.variableName !== 'string'
|
||||
) {
|
||||
return 'value'
|
||||
}
|
||||
return currentValue.variableName
|
||||
},
|
||||
defaultValue: (commandBarContext) => {
|
||||
const { currentValue } = commandBarContext.argumentsToSubmit
|
||||
if (
|
||||
!currentValue ||
|
||||
!(currentValue instanceof Object) ||
|
||||
!('valueText' in currentValue) ||
|
||||
typeof currentValue.valueText !== 'string'
|
||||
) {
|
||||
return KCL_DEFAULT_LENGTH
|
||||
}
|
||||
return currentValue.valueText
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'Text-to-CAD': {
|
||||
description: 'Use the Zoo Text-to-CAD API to generate part starters.',
|
||||
icon: 'chat',
|
||||
|
||||