Compare commits
58 Commits
jtran/requ
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
6e2274a6c4 | |||
4b9f71c994 | |||
e86a5622c8 | |||
a503d1ce50 | |||
11a94cc99e | |||
e295f82495 | |||
0b9cf2dc21 | |||
eb58507e93 | |||
ca28a5f549 | |||
6f4bbdb79e | |||
6773dbe7ff | |||
acfc2b47fa | |||
9dc7ff9797 | |||
308a0fb06e | |||
214ae6f512 | |||
8d54fec589 | |||
4bfbecd3e7 | |||
dff3848a00 | |||
f875efab1b | |||
3f082c8222 | |||
e1c45bdb33 | |||
5cb5dbd689 | |||
0a8881bc69 | |||
be9438160e | |||
77b565f781 | |||
c843dfad95 | |||
a3ff0a45eb | |||
4617ad0fed | |||
5fa51a2f92 | |||
4218777afb | |||
8b1b462e29 | |||
2bc99ba39b | |||
ffe0da6dcd | |||
d27afb8c74 | |||
1c778bf373 | |||
5df8a943a9 | |||
ab729dbcdb | |||
84865eaed0 | |||
543e809739 | |||
61b669cf4e | |||
75f1aaa824 | |||
f4848d7dea | |||
a0167f6ba6 | |||
e5a26c42e6 | |||
9c87b124d9 | |||
21389c089d | |||
29f57be8c1 | |||
cd55f07619 | |||
baf7d3dd9d | |||
54a9a50969 | |||
2830c750fa | |||
d3160cd85a | |||
fd1b4c3a32 | |||
b0a41c31ac | |||
5825ba575c | |||
e5bec2140e | |||
7bf6bc3048 | |||
22f9df73ed |
@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
|
||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,./src-tauri/gen/schemas
|
||||
|
48
.github/workflows/ci.yml
vendored
@ -20,6 +20,11 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
check-format:
|
||||
runs-on: 'ubuntu-latest'
|
||||
@ -84,8 +89,43 @@ jobs:
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:ci
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
|
||||
- name: Install Chromium Browser
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn playwright install chromium --with-deps
|
||||
|
||||
- name: run unit tests
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn test:nowatch
|
||||
env:
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
|
||||
- name: check for changes
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
id: git-check
|
||||
run: |
|
||||
git add src/lang/std/artifactMapGraphs
|
||||
if git status src/lang/std/artifactMapGraphs | grep -q "Changes to be committed"
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' && steps.git-check.outputs.modified == 'true' }}
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
# TODO when webkit works on ubuntu remove the os part of the commit message
|
||||
git commit -am "Look at this (photo)Graph *in the voice of Nickelback*" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
|
||||
|
||||
|
||||
- run: yarn test:nowatch
|
||||
|
||||
|
||||
prepare-json-files:
|
||||
@ -495,7 +535,7 @@ jobs:
|
||||
project_id: kittycadapi
|
||||
|
||||
- name: Upload release files to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
with:
|
||||
path: artifact
|
||||
glob: '*/Zoo*'
|
||||
@ -503,13 +543,13 @@ jobs:
|
||||
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
||||
|
||||
- name: Upload update endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
with:
|
||||
path: last_update.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload download endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.0
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
with:
|
||||
path: last_download.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
49
.github/workflows/generate-machine-api-types.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: generate machine-api types
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'openapi/machine-api.json'
|
||||
- '.github/workflows/generate-machine-api-types.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
generate:
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- run: yarn generate:machine-api
|
||||
- run: yarn fmt
|
||||
- name: check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
git add .
|
||||
if git status | grep -q "Changes to be committed"
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
run: |
|
||||
git add .
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
git commit -am "New machine-api types" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
|
67
.github/workflows/playwright.yml
vendored
@ -13,7 +13,7 @@ permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
@ -34,8 +34,13 @@ jobs:
|
||||
- 'src/wasm-lib/**'
|
||||
|
||||
playwright-ubuntu:
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
needs: check-rust-changes
|
||||
steps:
|
||||
- name: Tune GitHub-hosted runner network
|
||||
@ -106,13 +111,19 @@ jobs:
|
||||
- name: build web
|
||||
run: yarn build:local
|
||||
- name: Run ubuntu/chrome snapshots
|
||||
continue-on-error: true
|
||||
run: |
|
||||
yarn playwright test --project="Google Chrome" --update-snapshots e2e/playwright/snapshot-tests.spec.ts
|
||||
yarn playwright test --project="Google Chrome" --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-ubuntu-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- name: Clean up test-results
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
@ -143,7 +154,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
with:
|
||||
name: playwright-report-ubuntu-${{ github.sha }}
|
||||
name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
# if have previous run results, use them
|
||||
@ -151,7 +162,7 @@ jobs:
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-ubuntu-${{ github.sha }}
|
||||
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run ubuntu/chrome flow (with retries)
|
||||
id: retry
|
||||
@ -160,14 +171,14 @@ jobs:
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
yarn playwright test --project="Google Chrome" e2e/playwright/flow-tests.spec.ts || true
|
||||
yarn playwright test --project="Google Chrome" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true
|
||||
# # 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
|
||||
@ -175,7 +186,7 @@ jobs:
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
yarn playwright test --project="Google Chrome" --last-failed e2e/playwright/flow-tests.spec.ts || true
|
||||
yarn playwright test --project="Google Chrome" --last-failed --grep-invert=@snapshot || true
|
||||
# send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
retry=$((retry + 1))
|
||||
@ -188,9 +199,9 @@ jobs:
|
||||
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
|
||||
@ -210,21 +221,26 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-ubuntu-${{ github.sha }}
|
||||
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-ubuntu-${{ github.sha }}
|
||||
name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
playwright-macos:
|
||||
timeout-minutes: 60
|
||||
runs-on: macos-14-large
|
||||
timeout-minutes: 30
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
needs: check-rust-changes
|
||||
steps:
|
||||
- name: Tune GitHub-hosted runner network
|
||||
@ -300,7 +316,7 @@ jobs:
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-macos-${{ github.sha }}
|
||||
name: test-results-macos-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run macos/safari flow (with retries)
|
||||
id: retry
|
||||
@ -309,14 +325,14 @@ jobs:
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts || true
|
||||
yarn playwright test --project="webkit" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true
|
||||
# # 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
|
||||
@ -324,7 +340,7 @@ jobs:
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
yarn playwright test --project="webkit" --last-failed e2e/playwright/flow-tests.spec.ts || true
|
||||
yarn playwright test --project="webkit" --last-failed --grep-invert=@snapshot || true
|
||||
# send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
retry=$((retry + 1))
|
||||
@ -337,9 +353,9 @@ jobs:
|
||||
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
|
||||
@ -354,15 +370,14 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: test-results-macos-${{ github.sha }}
|
||||
name: test-results-macos-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: playwright-report-macos-${{ github.sha }}
|
||||
name: playwright-report-macos-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
|
2
.gitignore
vendored
@ -39,6 +39,7 @@ src/wasm-lib/grackle/test_json_output
|
||||
e2e/playwright/playwright-secrets.env
|
||||
e2e/playwright/temp1.png
|
||||
e2e/playwright/temp2.png
|
||||
e2e/playwright/temp3.png
|
||||
# exports from snapshot-tests.spec.ts "exports of each format should work"
|
||||
e2e/playwright/export-snapshots/*
|
||||
!e2e/playwright/export-snapshots/*.png
|
||||
@ -48,6 +49,7 @@ e2e/playwright/export-snapshots/*
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/src/lang/std/artifactMapCache
|
||||
|
||||
|
||||
## generated files
|
||||
|
11
README.md
@ -110,6 +110,17 @@ Note that these became separate apps on Macos, so make sure you open the right o
|
||||
|
||||
<img width="1232" alt="image (1)" src="https://user-images.githubusercontent.com/29681384/211947073-e76b4933-bef5-4636-bc4d-e930ac8e290f.png">
|
||||
|
||||
## Checking out commits / Bisecting
|
||||
|
||||
Which commands from setup are one off vs need to be run every time?
|
||||
|
||||
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
||||
```bash
|
||||
yarn install
|
||||
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
|
||||
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
||||
```
|
||||
|
||||
## Before submitting a PR
|
||||
|
||||
Before you submit a contribution PR to this repo, please ensure that:
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "abs"
|
||||
excerpt: "Computes the absolute value of a number."
|
||||
excerpt: "Compute the absolute value of a number."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the absolute value of a number.
|
||||
Compute the absolute value of a number.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "acos"
|
||||
excerpt: "Computes the arccosine of a number (in radians)."
|
||||
excerpt: "Compute the arccosine of a number (in radians)."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the arccosine of a number (in radians).
|
||||
Compute the arccosine of a number (in radians).
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "angleToMatchLengthX"
|
||||
excerpt: "Returns the angle to match the given length for x."
|
||||
excerpt: "Compute the angle (in degrees) in o"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Returns the angle to match the given length for x.
|
||||
Compute the angle (in degrees) in o
|
||||
|
||||
|
||||
|
||||
@ -253,6 +253,10 @@ const extrusion = extrude(5, sketch001)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -458,6 +462,10 @@ const extrusion = extrude(5, sketch001)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -257,6 +257,10 @@ const extrusion = extrude(5, sketch001)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -462,6 +466,10 @@ const extrusion = extrude(5, sketch001)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "angledLine"
|
||||
excerpt: "Draw an angled line."
|
||||
excerpt: "Draw a line segment relative to the current origin using the polar"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw an angled line.
|
||||
|
||||
Draw a line segment relative to the current origin using the polar
|
||||
|
||||
measure of some angle and distance.
|
||||
|
||||
```js
|
||||
angledLine(data: AngledLineData, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -172,6 +172,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -377,6 +381,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -584,6 +592,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -789,6 +801,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "angledLineOfXLength"
|
||||
excerpt: "Draw an angled line of a given x length."
|
||||
excerpt: "Create a line segment from the current 2-dimensional sketch origin"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw an angled line of a given x length.
|
||||
|
||||
Create a line segment from the current 2-dimensional sketch origin
|
||||
|
||||
along some angle (in degrees) for some relative length in the 'x' dimension.
|
||||
|
||||
```js
|
||||
angledLineOfXLength(data: AngledLineData, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -171,6 +171,10 @@ const extrusion = extrude(10, sketch001)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -376,6 +380,10 @@ const extrusion = extrude(10, sketch001)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -583,6 +591,10 @@ const extrusion = extrude(10, sketch001)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -788,6 +800,10 @@ const extrusion = extrude(10, sketch001)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "angledLineOfYLength"
|
||||
excerpt: "Draw an angled line of a given y length."
|
||||
excerpt: "Create a line segment from the current 2-dimensional sketch origin"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw an angled line of a given y length.
|
||||
|
||||
Create a line segment from the current 2-dimensional sketch origin
|
||||
|
||||
along some angle (in degrees) for some relative length in the 'y' dimension.
|
||||
|
||||
```js
|
||||
angledLineOfYLength(data: AngledLineData, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -173,6 +173,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -378,6 +382,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -585,6 +593,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -790,6 +802,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "angledLineThatIntersects"
|
||||
excerpt: "Draw an angled line that intersects with a given line."
|
||||
excerpt: "Draw an angled line from the current origin, constructing a line segment"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw an angled line that intersects with a given line.
|
||||
|
||||
Draw an angled line from the current origin, constructing a line segment
|
||||
|
||||
such that the newly created line intersects the desired target line segment.
|
||||
|
||||
```js
|
||||
angledLineThatIntersects(data: AngledLineThatIntersectsData, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -265,6 +265,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -470,6 +474,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -677,6 +685,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -882,6 +894,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "angledLineToX"
|
||||
excerpt: "Draw an angled line to a given x coordinate."
|
||||
excerpt: "Create a line segment from the current 2-dimensional sketch origin"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw an angled line to a given x coordinate.
|
||||
|
||||
Create a line segment from the current 2-dimensional sketch origin
|
||||
|
||||
along some angle (in degrees) for some length, ending at the provided value in the 'x' dimension.
|
||||
|
||||
```js
|
||||
angledLineToX(data: AngledLineToData, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -170,6 +170,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -375,6 +379,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -582,6 +590,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -787,6 +799,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "angledLineToY"
|
||||
excerpt: "Draw an angled line to a given y coordinate."
|
||||
excerpt: "Create a line segment from the current 2-dimensional sketch origin"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw an angled line to a given y coordinate.
|
||||
|
||||
Create a line segment from the current 2-dimensional sketch origin
|
||||
|
||||
along some angle (in degrees) for some length, ending at the provided value in the 'y' dimension.
|
||||
|
||||
```js
|
||||
angledLineToY(data: AngledLineToData, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -170,6 +170,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -375,6 +379,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -582,6 +590,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -787,6 +799,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,14 @@
|
||||
---
|
||||
title: "arc"
|
||||
excerpt: "Draw an arc."
|
||||
excerpt: "Starting at the current sketch's origin, draw a curved line segment along"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw an arc.
|
||||
|
||||
Starting at the current sketch's origin, draw a curved line segment along
|
||||
|
||||
an imaginary circle of the specified radius.
|
||||
The arc is constructed such that the current position of the sketch is placed along an imaginary circle of the specified radius, at angleStart degrees. The resulting arc is the segment of the imaginary circle from that origin point to angleEnd, radius away from the center of the imaginary circle.
|
||||
Unless this makes a lot of sense and feels like what you're looking for to construct your shape, you're likely looking for tangentialArc.
|
||||
|
||||
```js
|
||||
arc(data: ArcData, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -181,6 +183,10 @@ const exampleSketch = startSketchOn('XZ')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -386,6 +392,10 @@ const exampleSketch = startSketchOn('XZ')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -593,6 +603,10 @@ const exampleSketch = startSketchOn('XZ')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -798,6 +812,10 @@ const exampleSketch = startSketchOn('XZ')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "asin"
|
||||
excerpt: "Computes the arcsine of a number (in radians)."
|
||||
excerpt: "Compute the arcsine of a number (in radians)."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the arcsine of a number (in radians).
|
||||
Compute the arcsine of a number (in radians).
|
||||
|
||||
|
||||
|
||||
|
37
docs/kcl/assertEqual.md
Normal file
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "atan"
|
||||
excerpt: "Computes the arctangent of a number (in radians)."
|
||||
excerpt: "Compute the arctangent of a number (in radians)."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the arctangent of a number (in radians).
|
||||
Compute the arctangent of a number (in radians).
|
||||
|
||||
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "bezierCurve"
|
||||
excerpt: "Draw a bezier curve."
|
||||
excerpt: "Draw a smooth, continuous, curved line segment from the current origin to"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a bezier curve.
|
||||
|
||||
Draw a smooth, continuous, curved line segment from the current origin to
|
||||
|
||||
the desired (x, y), using a number of control points to shape the curve's shape.
|
||||
|
||||
```js
|
||||
bezierCurve(data: BezierData, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -176,6 +176,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -381,6 +385,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -588,6 +596,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -793,6 +805,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "ceil"
|
||||
excerpt: "Computes the smallest integer greater than or equal to a number."
|
||||
excerpt: "Compute the smallest integer greater than or equal to a number."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the smallest integer greater than or equal to a number.
|
||||
Compute the smallest integer greater than or equal to a number.
|
||||
|
||||
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "chamfer"
|
||||
excerpt: "Create chamfers on tagged paths."
|
||||
excerpt: "Cut a straight transitional edge along a tagged path."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Create chamfers on tagged paths.
|
||||
|
||||
Cut a straight transitional edge along a tagged path.
|
||||
|
||||
Chamfer is similar in function and use to a fillet, except a fillet will blend the transition along an edge, rather than cut a sharp, straight transitional edge.
|
||||
|
||||
```js
|
||||
chamfer(data: ChamferData, extrude_group: ExtrudeGroup, tag?: TagDeclarator) -> ExtrudeGroup
|
||||
@ -398,6 +398,10 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -798,6 +802,10 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "circle"
|
||||
excerpt: "Sketch a circle."
|
||||
excerpt: "Construct a 2-dimensional circle, of the specified radius, centered at"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Sketch a circle.
|
||||
|
||||
Construct a 2-dimensional circle, of the specified radius, centered at
|
||||
|
||||
the provided (x, y) origin point.
|
||||
|
||||
```js
|
||||
circle(center: [number], radius: number, sketch_surface_or_group: SketchSurfaceOrGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -169,6 +169,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -541,6 +545,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -748,6 +756,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -953,6 +965,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "close"
|
||||
excerpt: "Close the current sketch."
|
||||
excerpt: "Construct a line segment from the current origin back to the profile's"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Close the current sketch.
|
||||
|
||||
Construct a line segment from the current origin back to the profile's
|
||||
|
||||
origin, ensuring the resulting 2-dimensional sketch is not open-ended.
|
||||
|
||||
```js
|
||||
close(sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -171,6 +171,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -376,6 +380,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -583,6 +591,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -788,6 +800,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "cos"
|
||||
excerpt: "Computes the cosine of a number (in radians)."
|
||||
excerpt: "Compute the cosine of a number (in radians)."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the cosine of a number (in radians).
|
||||
Compute the cosine of a number (in radians).
|
||||
|
||||
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "extrude"
|
||||
excerpt: "Extrudes by a given amount."
|
||||
excerpt: "Extend a 2-dimensional sketch through a third dimension in order to"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Extrudes by a given amount.
|
||||
|
||||
Extend a 2-dimensional sketch through a third dimension in order to
|
||||
|
||||
create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid.
|
||||
|
||||
```js
|
||||
extrude(length: number, sketch_group_set: SketchGroupSet) -> ExtrudeGroupSet
|
||||
@ -196,6 +196,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -402,6 +406,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -728,6 +736,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "fillet"
|
||||
excerpt: "Create fillets on tagged paths."
|
||||
excerpt: "Blend a transitional edge along a tagged path, smoothing the sharp edge."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Create fillets on tagged paths.
|
||||
|
||||
Blend a transitional edge along a tagged path, smoothing the sharp edge.
|
||||
|
||||
Fillet is similar in function and use to a chamfer, except a chamfer will cut a sharp transition along an edge while fillet will smoothly blend the transition.
|
||||
|
||||
```js
|
||||
fillet(data: FilletData, extrude_group: ExtrudeGroup, tag?: TagDeclarator) -> ExtrudeGroup
|
||||
@ -398,6 +398,10 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -798,6 +802,10 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "floor"
|
||||
excerpt: "Computes the largest integer less than or equal to a number."
|
||||
excerpt: "Compute the largest integer less than or equal to a number."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the largest integer less than or equal to a number.
|
||||
Compute the largest integer less than or equal to a number.
|
||||
|
||||
|
||||
|
||||
|
@ -299,6 +299,10 @@ const part001 = startSketchOn('XY')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -690,6 +694,10 @@ const part001 = startSketchOn('XY')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "hole"
|
||||
excerpt: "Use a sketch to cut a hole in another sketch."
|
||||
excerpt: "Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Use a sketch to cut a hole in another sketch.
|
||||
Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch.
|
||||
|
||||
|
||||
|
||||
@ -182,6 +182,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -388,6 +392,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -586,6 +594,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -791,6 +803,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -989,6 +1005,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -1194,6 +1214,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -6,8 +6,8 @@ layout: manual
|
||||
|
||||
Import a CAD file.
|
||||
|
||||
For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters. Otherwise you can specify the unit by passing in the options parameter. If you import a gltf file, we will try to find the bin file and import it as well.
|
||||
Import paths are relative to the current project directory. This only works in the desktop app not in browser.
|
||||
For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.
|
||||
Note: The import command currently only works when using the native Modeling App.
|
||||
|
||||
```js
|
||||
import(file_path: String, options?: ImportFormat) -> ImportedGeometry
|
||||
|
@ -21,6 +21,7 @@ layout: manual
|
||||
* [`arc`](kcl/arc)
|
||||
* [`asin`](kcl/asin)
|
||||
* [`assert`](kcl/assert)
|
||||
* [`assertEqual`](kcl/assertEqual)
|
||||
* [`assertGreaterThan`](kcl/assertGreaterThan)
|
||||
* [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq)
|
||||
* [`assertLessThan`](kcl/assertLessThan)
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "int"
|
||||
excerpt: "Converts a number to an integer."
|
||||
excerpt: "Convert a number to an integer."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Converts a number to an integer.
|
||||
Convert a number to an integer.
|
||||
|
||||
Callers should use floor(), ceil(), or other rounding function first if they care about how numbers with fractional parts are converted. If the number has a fractional part, it's truncated, moving the number towards zero.
|
||||
If the number is NaN or has a magnitude, either positive or negative, that is too large to fit into the internal integer representation, the result is a runtime error.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "lastSegY"
|
||||
excerpt: "Returns the last segment of y."
|
||||
excerpt: "Extract the 'y' axis value of the last line segment in the provided 2-d"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Returns the last segment of y.
|
||||
|
||||
Extract the 'y' axis value of the last line segment in the provided 2-d
|
||||
|
||||
sketch.
|
||||
|
||||
```js
|
||||
lastSegY(sketch_group: SketchGroup) -> number
|
||||
@ -162,6 +162,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -367,6 +371,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "legAngX"
|
||||
excerpt: "Returns the angle of the given leg for x."
|
||||
excerpt: "Compute the angle of the given leg for x."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Returns the angle of the given leg for x.
|
||||
Compute the angle of the given leg for x.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "legAngY"
|
||||
excerpt: "Returns the angle of the given leg for y."
|
||||
excerpt: "Compute the angle of the given leg for y."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Returns the angle of the given leg for y.
|
||||
Compute the angle of the given leg for y.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "legLen"
|
||||
excerpt: "Returns the length of the given leg."
|
||||
excerpt: "Compute the length of the given leg."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Returns the length of the given leg.
|
||||
Compute the length of the given leg.
|
||||
|
||||
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "line"
|
||||
excerpt: "Draw a line."
|
||||
excerpt: "Draw a line relative to the current origin to a specified (x, y) away"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line.
|
||||
|
||||
Draw a line relative to the current origin to a specified (x, y) away
|
||||
|
||||
from the current position.
|
||||
|
||||
```js
|
||||
line(delta: [number], sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -175,6 +175,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -380,6 +384,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -587,6 +595,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -792,6 +804,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "lineTo"
|
||||
excerpt: "Draw a line to a point."
|
||||
excerpt: "Draw a line from the current origin to some absolute (x, y) point."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line to a point.
|
||||
Draw a line from the current origin to some absolute (x, y) point.
|
||||
|
||||
|
||||
|
||||
@ -162,6 +162,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -367,6 +371,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -574,6 +582,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -779,6 +791,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "ln"
|
||||
excerpt: "Computes the natural logarithm of the number."
|
||||
excerpt: "Compute the natural logarithm of the number."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the natural logarithm of the number.
|
||||
Compute the natural logarithm of the number.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "log"
|
||||
excerpt: "Computes the logarithm of the number with respect to an arbitrary base."
|
||||
excerpt: "Compute the logarithm of the number with respect to an arbitrary base."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the logarithm of the number with respect to an arbitrary base.
|
||||
Compute the logarithm of the number with respect to an arbitrary base.
|
||||
|
||||
The result might not be correctly rounded owing to implementation details; `log2()` can produce more accurate results for base 2, and `log10()` can produce more accurate results for base 10.
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "log10"
|
||||
excerpt: "Computes the base 10 logarithm of the number."
|
||||
excerpt: "Compute the base 10 logarithm of the number."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the base 10 logarithm of the number.
|
||||
Compute the base 10 logarithm of the number.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "log2"
|
||||
excerpt: "Computes the base 2 logarithm of the number."
|
||||
excerpt: "Compute the base 2 logarithm of the number."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the base 2 logarithm of the number.
|
||||
Compute the base 2 logarithm of the number.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "max"
|
||||
excerpt: "Computes the maximum of the given arguments."
|
||||
excerpt: "Compute the maximum of the given arguments."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the maximum of the given arguments.
|
||||
Compute the maximum of the given arguments.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "min"
|
||||
excerpt: "Computes the minimum of the given arguments."
|
||||
excerpt: "Compute the minimum of the given arguments."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the minimum of the given arguments.
|
||||
Compute the minimum of the given arguments.
|
||||
|
||||
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "patternCircular2d"
|
||||
excerpt: "A circular pattern on a 2D sketch."
|
||||
excerpt: "Repeat a 2-dimensional sketch some number of times along a partial or"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A circular pattern on a 2D sketch.
|
||||
|
||||
Repeat a 2-dimensional sketch some number of times along a partial or
|
||||
|
||||
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
|
||||
|
||||
```js
|
||||
patternCircular2d(data: CircularPattern2dData, sketch_group_set: SketchGroupSet) -> [SketchGroup]
|
||||
@ -180,6 +180,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -386,6 +390,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "patternCircular3d"
|
||||
excerpt: "A circular pattern on a 3D model."
|
||||
excerpt: "Repeat a 3-dimensional solid some number of times along a partial or"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A circular pattern on a 3D model.
|
||||
|
||||
Repeat a 3-dimensional solid some number of times along a partial or
|
||||
|
||||
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
|
||||
|
||||
```js
|
||||
patternCircular3d(data: CircularPattern3dData, extrude_group_set: ExtrudeGroupSet) -> [ExtrudeGroup]
|
||||
@ -304,6 +304,10 @@ const example = extrude(-5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "patternLinear2d"
|
||||
excerpt: "A linear pattern on a 2D sketch."
|
||||
excerpt: "Repeat a 2-dimensional sketch along some dimension, with a dynamic amount"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A linear pattern on a 2D sketch.
|
||||
|
||||
Repeat a 2-dimensional sketch along some dimension, with a dynamic amount
|
||||
|
||||
of distance between each repetition, some specified number of times.
|
||||
|
||||
```js
|
||||
patternLinear2d(data: LinearPattern2dData, sketch_group_set: SketchGroupSet) -> [SketchGroup]
|
||||
@ -173,6 +173,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -379,6 +383,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "patternLinear3d"
|
||||
excerpt: "A linear pattern on a 3D model."
|
||||
excerpt: "Repeat a 3-dimensional solid along a linear path, with a dynamic amount"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A linear pattern on a 3D model.
|
||||
|
||||
Repeat a 3-dimensional solid along a linear path, with a dynamic amount
|
||||
|
||||
of distance between each repetition, some specified number of times.
|
||||
|
||||
```js
|
||||
patternLinear3d(data: LinearPattern3dData, extrude_group_set: ExtrudeGroupSet) -> [ExtrudeGroup]
|
||||
@ -302,6 +302,10 @@ const example = extrude(1, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "patternTransform"
|
||||
excerpt: "A linear pattern on a 3D solid."
|
||||
excerpt: "Repeat a 3-dimensional solid by successively applying a transformation (such"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A linear pattern on a 3D solid.
|
||||
Repeat a 3-dimensional solid by successively applying a transformation (such
|
||||
|
||||
Each repetition of the pattern can be transformed (e.g. scaled, translated, hidden, etc).
|
||||
as rotation, scale, translation, visibility) on each repetition.
|
||||
|
||||
```js
|
||||
patternTransform(num_repetitions: u32, transform_function: FunctionParam, extrude_group_set: ExtrudeGroupSet) -> [ExtrudeGroup]
|
||||
@ -304,6 +304,10 @@ let vase = layer()
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "polar"
|
||||
excerpt: "Convert from polar/sphere coordinates to cartesian coordinates."
|
||||
excerpt: "Convert polar/sphere (azimuth, elevation, distance) coordinates to"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert from polar/sphere coordinates to cartesian coordinates.
|
||||
|
||||
Convert polar/sphere (azimuth, elevation, distance) coordinates to
|
||||
|
||||
cartesian (x/y/z grid) coordinates.
|
||||
|
||||
```js
|
||||
polar(data: PolarCoordsData) -> [number]
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "pow"
|
||||
excerpt: "Computes the number to a power."
|
||||
excerpt: "Compute the number to a power."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the number to a power.
|
||||
Compute the number to a power.
|
||||
|
||||
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "profileStart"
|
||||
excerpt: ""
|
||||
excerpt: "Extract the provided 2-dimensional sketch group's profile's origin"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Extract the provided 2-dimensional sketch group's profile's origin
|
||||
|
||||
|
||||
|
||||
value.
|
||||
|
||||
```js
|
||||
profileStart(sketch_group: SketchGroup) -> [number]
|
||||
@ -163,6 +163,10 @@ const sketch001 = startSketchOn('XY')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -368,6 +372,10 @@ const sketch001 = startSketchOn('XY')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "profileStartX"
|
||||
excerpt: ""
|
||||
excerpt: "Extract the provided 2-dimensional sketch group's profile's origin's 'x'"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Extract the provided 2-dimensional sketch group's profile's origin's 'x'
|
||||
|
||||
|
||||
|
||||
value.
|
||||
|
||||
```js
|
||||
profileStartX(sketch_group: SketchGroup) -> number
|
||||
@ -158,6 +158,10 @@ const sketch001 = startSketchOn('XY')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -363,6 +367,10 @@ const sketch001 = startSketchOn('XY')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "profileStartY"
|
||||
excerpt: ""
|
||||
excerpt: "Extract the provided 2-dimensional sketch group's profile's origin's 'y'"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Extract the provided 2-dimensional sketch group's profile's origin's 'y'
|
||||
|
||||
|
||||
|
||||
value.
|
||||
|
||||
```js
|
||||
profileStartY(sketch_group: SketchGroup) -> number
|
||||
@ -157,6 +157,10 @@ const sketch001 = startSketchOn('XY')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -362,6 +366,10 @@ const sketch001 = startSketchOn('XY')
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "revolve"
|
||||
excerpt: "Revolve a sketch around an axis."
|
||||
excerpt: "Rotate a sketch around some provided axis, creating a solid from its extent."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Revolve a sketch around an axis.
|
||||
|
||||
Rotate a sketch around some provided axis, creating a solid from its extent.
|
||||
|
||||
This, like extrude, is able to create a 3-dimensional solid from a 2-dimensional sketch. However, unlike extrude, this creates a solid by using the extent of the sketch as its revolved around an axis rather than using the extent of the sketch linearly translated through a third dimension.
|
||||
|
||||
```js
|
||||
revolve(data: RevolveData, sketch_group: SketchGroup) -> ExtrudeGroup
|
||||
@ -379,6 +379,10 @@ uuid |
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -584,6 +588,10 @@ uuid |
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -907,6 +915,10 @@ uuid |
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "segAng"
|
||||
excerpt: "Returns the angle of the segment."
|
||||
excerpt: "Compute the angle (in degrees) of the provided line segment."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Returns the angle of the segment.
|
||||
Compute the angle (in degrees) of the provided line segment.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "segEndX"
|
||||
excerpt: "Returns the segment end of x."
|
||||
excerpt: "Compute the ending point of the provided line segment along the 'x' axis."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Returns the segment end of x.
|
||||
Compute the ending point of the provided line segment along the 'x' axis.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "segEndY"
|
||||
excerpt: "Returns the segment end of y."
|
||||
excerpt: "Compute the ending point of the provided line segment along the 'y' axis."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Returns the segment end of y.
|
||||
Compute the ending point of the provided line segment along the 'y' axis.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "segLen"
|
||||
excerpt: "Returns the length of the segment."
|
||||
excerpt: "Compute the length of the provided line segment."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Returns the length of the segment.
|
||||
Compute the length of the provided line segment.
|
||||
|
||||
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "shell"
|
||||
excerpt: "Shell a solid."
|
||||
excerpt: "Remove volume from a 3-dimensional shape such that a wall of the"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Shell a solid.
|
||||
|
||||
Remove volume from a 3-dimensional shape such that a wall of the
|
||||
|
||||
provided thickness remains, taking volume starting at the provided face, leaving it open in that direction.
|
||||
|
||||
```js
|
||||
shell(data: ShellData, extrude_group: ExtrudeGroup) -> ExtrudeGroup
|
||||
@ -386,6 +386,10 @@ shell({ faces: ['end'], thickness: 0.25 }, firstSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -777,6 +781,10 @@ shell({ faces: ['end'], thickness: 0.25 }, firstSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "sin"
|
||||
excerpt: "Computes the sine of a number (in radians)."
|
||||
excerpt: "Compute the sine of a number (in radians)."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the sine of a number (in radians).
|
||||
Compute the sine of a number (in radians).
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "sqrt"
|
||||
excerpt: "Computes the square root of a number."
|
||||
excerpt: "Compute the square root of a number."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the square root of a number.
|
||||
Compute the square root of a number.
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "startProfileAt"
|
||||
excerpt: "Start a profile at a given point."
|
||||
excerpt: "Start a new profile at a given point."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Start a profile at a given point.
|
||||
Start a new profile at a given point.
|
||||
|
||||
|
||||
|
||||
@ -241,6 +241,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -540,6 +544,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -745,6 +753,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "startSketchAt"
|
||||
excerpt: "Start a sketch at a given point on the 'XY' plane."
|
||||
excerpt: "Start a new 2-dimensional sketch at a given point on the 'XY' plane."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Start a sketch at a given point on the 'XY' plane.
|
||||
Start a new 2-dimensional sketch at a given point on the 'XY' plane.
|
||||
|
||||
|
||||
|
||||
@ -188,6 +188,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -393,6 +397,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "startSketchOn"
|
||||
excerpt: "Start a sketch on a specific plane or face."
|
||||
excerpt: "Start a new 2-dimensional sketch on a specific plane or face."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Start a sketch on a specific plane or face.
|
||||
Start a new 2-dimensional sketch on a specific plane or face.
|
||||
|
||||
|
||||
|
||||
@ -310,6 +310,10 @@ const a1 = startSketchOn({
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -722,6 +726,10 @@ const a1 = startSketchOn({
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
2382
docs/kcl/std.json
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "tan"
|
||||
excerpt: "Computes the tangent of a number (in radians)."
|
||||
excerpt: "Compute the tangent of a number (in radians)."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Computes the tangent of a number (in radians).
|
||||
Compute the tangent of a number (in radians).
|
||||
|
||||
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
---
|
||||
title: "tangentialArc"
|
||||
excerpt: "Draw an arc."
|
||||
excerpt: "Starting at the current sketch's origin, draw a curved line segment along"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw an arc.
|
||||
|
||||
Starting at the current sketch's origin, draw a curved line segment along
|
||||
|
||||
some part of an imaginary circle of the specified radius.
|
||||
The arc is constructed such that the last line segment is placed tangent to the imaginary circle of the specified radius. The resulting arc is the segment of the imaginary circle from that tangent point for 'offset' degrees along the imaginary circle.
|
||||
|
||||
```js
|
||||
tangentialArc(data: TangentialArcData, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -171,6 +172,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -376,6 +381,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -583,6 +592,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -788,6 +801,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "tangentialArcTo"
|
||||
excerpt: "Draw an arc."
|
||||
excerpt: "Starting at the current sketch's origin, draw a curved line segment along"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw an arc.
|
||||
|
||||
Starting at the current sketch's origin, draw a curved line segment along
|
||||
|
||||
some part of an imaginary circle until it reaches the desired (x, y) coordinates.
|
||||
|
||||
```js
|
||||
tangentialArcTo(to: [number], sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -162,6 +162,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -367,6 +371,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -574,6 +582,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -779,6 +791,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "xLine"
|
||||
excerpt: "Draw a line on the x-axis."
|
||||
excerpt: "Draw a line relative to the current origin to a specified distance away"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line on the x-axis.
|
||||
|
||||
Draw a line relative to the current origin to a specified distance away
|
||||
|
||||
from the current position along the 'x' axis.
|
||||
|
||||
```js
|
||||
xLine(length: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -165,6 +165,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -370,6 +374,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -577,6 +585,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -782,6 +794,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "xLineTo"
|
||||
excerpt: "Draw a line to a point on the x-axis."
|
||||
excerpt: "Draw a line parallel to the X axis, that ends at the given X."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line to a point on the x-axis.
|
||||
|
||||
Draw a line parallel to the X axis, that ends at the given X.
|
||||
|
||||
E.g. if the previous line ended at (1, 1), then xLineTo(4) draws a line from (1, 1) to (4, 1)
|
||||
|
||||
```js
|
||||
xLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -165,6 +165,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -370,6 +374,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -577,6 +585,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -782,6 +794,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "yLine"
|
||||
excerpt: "Draw a line on the y-axis."
|
||||
excerpt: "Draw a line relative to the current origin to a specified distance away"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line on the y-axis.
|
||||
|
||||
Draw a line relative to the current origin to a specified distance away
|
||||
|
||||
from the current position along the 'y' axis.
|
||||
|
||||
```js
|
||||
yLine(length: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -163,6 +163,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -368,6 +372,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -575,6 +583,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -780,6 +792,10 @@ const example = extrude(10, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
@ -1,12 +1,12 @@
|
||||
---
|
||||
title: "yLineTo"
|
||||
excerpt: "Draw a line to a point on the y-axis."
|
||||
excerpt: "Draw a line parallel to the Y axis, that ends at the given Y."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Draw a line to a point on the y-axis.
|
||||
|
||||
Draw a line parallel to the Y axis, that ends at the given Y.
|
||||
|
||||
E.g. if the previous line ended at (1, 1), then yLineTo(4) draws a line from (1, 1) to (1, 4)
|
||||
|
||||
```js
|
||||
yLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||
@ -161,6 +161,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -366,6 +370,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -573,6 +581,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
@ -778,6 +790,10 @@ const example = extrude(5, exampleSketch)
|
||||
type: "TangentialArcTo",
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
from: [number, number],
|
||||
// The tag of the path.
|
||||
|
153
e2e/playwright/basic-sketch.spec.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import {
|
||||
getUtils,
|
||||
TEST_COLORS,
|
||||
setup,
|
||||
tearDown,
|
||||
commonPoints,
|
||||
PERSIST_MODELING_CONTEXT,
|
||||
} from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.setTimeout(120000)
|
||||
|
||||
async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
// If we have the code pane open, we should see the code.
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(``)
|
||||
} else {
|
||||
// Ensure we don't see the code.
|
||||
await expect(u.codeLocator).not.toBeVisible()
|
||||
}
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ')`
|
||||
)
|
||||
}
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.waitForTimeout(1000) // TODO detect animation ending, or disable animation
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
}
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)`)
|
||||
}
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)`)
|
||||
} else {
|
||||
await page.waitForTimeout(500)
|
||||
}
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
||||
|> line([-${commonPoints.num2}, 0], %)`)
|
||||
}
|
||||
|
||||
// deselect line tool
|
||||
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
|
||||
if (openPanes.includes('code')) {
|
||||
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.WHITE)).toBeLessThan(3)
|
||||
await expect(
|
||||
await u.getGreatestPixDiff(line1, [249, 249, 249])
|
||||
).toBeLessThan(3)
|
||||
}
|
||||
// click between first two clicks to get center of the line
|
||||
await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
|
||||
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
|
||||
}
|
||||
|
||||
// hold down shift
|
||||
await page.keyboard.down('Shift')
|
||||
// click between the latest two clicks to get center of the line
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 20)
|
||||
|
||||
// selected two lines therefore there should be two cursors
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Length: open menu' }).click()
|
||||
await page.getByRole('button', { name: 'Equal Length' }).click()
|
||||
|
||||
// Open the code pane.
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %, $seg01)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
||||
|> angledLine([180, segLen(seg01)], %)`)
|
||||
}
|
||||
|
||||
test.describe('Basic sketch', () => {
|
||||
test('code pane open at start', async ({ page }) => {
|
||||
await doBasicSketch(page, ['code'])
|
||||
})
|
||||
|
||||
test('code pane closed at start', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async (persistModelingContext) => {
|
||||
localStorage.setItem(
|
||||
persistModelingContext,
|
||||
JSON.stringify({ openPanes: [] })
|
||||
)
|
||||
}, PERSIST_MODELING_CONTEXT)
|
||||
await doBasicSketch(page, [])
|
||||
})
|
||||
})
|
@ -0,0 +1,111 @@
|
||||
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 }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
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: any,
|
||||
plane: string,
|
||||
clickCoords: { x: number; y: number }
|
||||
) => {
|
||||
const u = await getUtils(page)
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const coord =
|
||||
plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100
|
||||
const camCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
vantage: { x: coord, y: coord, z: coord },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
}
|
||||
const updateCamCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
}
|
||||
|
||||
const code = `const sketch001 = startSketchOn('${plane}')
|
||||
|> startProfileAt([0.9, -1.22], %)`
|
||||
|
||||
await u.openDebugPanel()
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
await u.sendCustomCmd(camCommand)
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd(updateCamCommand)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(clickCoords.x, clickCoords.y)
|
||||
await page.waitForTimeout(300) // wait for animation
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Line', exact: true })
|
||||
).toBeVisible()
|
||||
|
||||
// draw a line
|
||||
const startXPx = 600
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
|
||||
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await u.removeCurrentCode()
|
||||
}
|
||||
test('XY', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(
|
||||
page,
|
||||
'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 }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, 'YZ', { x: 700, y: 250 }) // green plane
|
||||
})
|
||||
|
||||
test('XZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 80 }) // blue plane
|
||||
})
|
||||
|
||||
test('-XY', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, '-XY', { x: 600, y: 118 }) // back of red plane
|
||||
})
|
||||
|
||||
test('-YZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, '-YZ', { x: 700, y: 219 }) // back of green plane
|
||||
})
|
||||
|
||||
test('-XZ', async ({ page }) => {
|
||||
await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 427 }) // back of blue plane
|
||||
})
|
||||
})
|
219
e2e/playwright/code-pane-and-errors.spec.ts
Normal file
@ -0,0 +1,219 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
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,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Ensure no badge is present
|
||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||
await expect(codePaneButtonHolder).not.toContainText('notification')
|
||||
|
||||
// Delete a character to break the KCL
|
||||
await u.openKclCodePanel()
|
||||
await page.getByText('extrude(').click()
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
// Ensure that a badge appears on the button
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
})
|
||||
|
||||
test('Opening and closing the code pane will consistently show error diagnostics', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 900 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Ensure we have no errors in the gutter.
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Ensure no badge is present
|
||||
const codePaneButton = page.getByRole('button', { name: 'KCL Code pane' })
|
||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||
await expect(codePaneButtonHolder).not.toContainText('notification')
|
||||
|
||||
// Delete a character to break the KCL
|
||||
await u.openKclCodePanel()
|
||||
await page.getByText('extrude(').click()
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
// Ensure that a badge appears on the button
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
|
||||
// Ensure we have an error diagnostic.
|
||||
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()
|
||||
|
||||
// Close the code pane
|
||||
await codePaneButton.click()
|
||||
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// Ensure that a badge appears on the button
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
// Ensure we have no errors in the gutter.
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Open the code pane
|
||||
await u.openKclCodePanel()
|
||||
|
||||
// Ensure that a badge appears on the button
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
|
||||
// Ensure we have an error diagnostic.
|
||||
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()
|
||||
})
|
||||
|
||||
test('When error is not in view you can click the badge to scroll to it', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Ensure badge is present
|
||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
|
||||
// Ensure we have no errors in the gutter, since error out of view.
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Click the badge.
|
||||
const badge = page.locator('#code-badge')
|
||||
await expect(badge).toBeVisible()
|
||||
await badge.click()
|
||||
|
||||
// Ensure we have an error diagnostic.
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
|
||||
// Hover over the error to see the error message
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(
|
||||
page
|
||||
.getByText(
|
||||
'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 ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Ensure badge is present
|
||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||
await expect(codePaneButtonHolder).toContainText('notification')
|
||||
|
||||
// Ensure we have no errors in the gutter, since error out of view.
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// click in the editor to focus it
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// go to the start of the editor and enter more text which will trigger
|
||||
// a lint error.
|
||||
// GO to the start of the editor.
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('Home')
|
||||
await page.keyboard.type('const foo_bar = 1')
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// ensure we have a lint error
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
|
||||
// Click the badge.
|
||||
const badge = page.locator('#code-badge')
|
||||
await expect(badge).toBeVisible()
|
||||
await badge.click()
|
||||
|
||||
// Ensure we have an error diagnostic.
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
|
||||
// Hover over the error to see the error message
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(
|
||||
page
|
||||
.getByText(
|
||||
'sketch profile must lie entirely on one side of the revolution axis'
|
||||
)
|
||||
.first()
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
363
e2e/playwright/command-bar-tests.spec.ts
Normal file
@ -0,0 +1,363 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
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,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click the line of code for xLine.
|
||||
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
test('Fillet from command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(-10, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const selectSegment = () => page.getByText(`line([0, -10], %)`).click()
|
||||
|
||||
await selectSegment()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByRole('button', { name: 'Fillet' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toContainText(
|
||||
`fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)`
|
||||
)
|
||||
})
|
||||
|
||||
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||
page,
|
||||
}) => {
|
||||
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')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'theme',
|
||||
exact: false,
|
||||
})
|
||||
const commandLevelArgButton = page.getByRole('button', { name: 'level' })
|
||||
const commandThemeArgButton = page.getByRole('button', { name: 'value' })
|
||||
// This selector changes after we set the setting
|
||||
let commandOptionInput = page.getByPlaceholder('Select an option')
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
// First try opening the command bar and closing it
|
||||
await page
|
||||
.getByRole('button', { name: 'Commands', exact: false })
|
||||
.or(page.getByRole('button', { name: '⌘K' }))
|
||||
.click()
|
||||
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(cmdSearchBar).not.toBeVisible()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
// Try typing in the command bar
|
||||
await cmdSearchBar.fill('theme')
|
||||
await expect(themeOption).toBeVisible()
|
||||
await themeOption.click()
|
||||
const themeInput = page.getByPlaceholder('Select an option')
|
||||
await expect(themeInput).toBeVisible()
|
||||
await expect(themeInput).toBeFocused()
|
||||
// Select dark theme
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active'
|
||||
)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "system" for this project`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
|
||||
commandOptionInput = page.getByPlaceholder('system')
|
||||
|
||||
// Test case for https://github.com/KittyCAD/modeling-app/issues/2882
|
||||
await commandBarButton.click()
|
||||
await cmdSearchBar.focus()
|
||||
await cmdSearchBar.fill('theme')
|
||||
await themeOption.click()
|
||||
await expect(commandThemeArgButton).toBeDisabled()
|
||||
await commandOptionInput.focus()
|
||||
await commandOptionInput.fill('lig')
|
||||
await commandLevelArgButton.click()
|
||||
await expect(commandLevelArgButton).toBeDisabled()
|
||||
|
||||
// Test case for https://github.com/KittyCAD/modeling-app/issues/2881
|
||||
await commandThemeArgButton.click()
|
||||
await expect(commandThemeArgButton).toBeDisabled()
|
||||
await expect(commandLevelArgButton).toHaveText('level: project')
|
||||
})
|
||||
|
||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
// Put the cursor in the code editor
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
// Try typing in the command bar
|
||||
await cmdSearchBar.fill('theme')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'Settings · app · theme',
|
||||
})
|
||||
await expect(themeOption).toBeVisible()
|
||||
await themeOption.click()
|
||||
const themeInput = page.getByPlaceholder('Select an option')
|
||||
await expect(themeInput).toBeVisible()
|
||||
await expect(themeInput).toBeFocused()
|
||||
// Select dark theme
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active'
|
||||
)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "system" for this project`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
})
|
||||
|
||||
test('Can extrude from the command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const distance = sqrt(20)
|
||||
const 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.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Make sure the stream is up
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await page.keyboard.press('Meta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
// Search for extrude command and choose it
|
||||
await page.getByRole('option', { name: 'Extrude' }).click()
|
||||
|
||||
// Assert that we're on the selection step
|
||||
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
|
||||
// Select a face
|
||||
await page.mouse.move(700, 200)
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
// Assert that we're on the distance step
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'distance', exact: false })
|
||||
).toBeDisabled()
|
||||
|
||||
// Assert that the an alternative variable name is chosen,
|
||||
// since the default variable name is already in use (distance)
|
||||
await page.getByRole('button', { name: 'Create new variable' }).click()
|
||||
await expect(page.getByPlaceholder('Variable name')).toHaveValue(
|
||||
'distance001'
|
||||
)
|
||||
|
||||
const continueButton = page.getByRole('button', { name: 'Continue' })
|
||||
const submitButton = page.getByRole('button', { name: 'Submit command' })
|
||||
await continueButton.click()
|
||||
|
||||
// Review step and argument hotkeys
|
||||
await expect(submitButton).toBeEnabled()
|
||||
await expect(submitButton).toBeFocused()
|
||||
await submitButton.press('Backspace')
|
||||
|
||||
// Assert we're back on the distance step
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'distance', exact: false })
|
||||
).toBeDisabled()
|
||||
|
||||
await continueButton.click()
|
||||
await submitButton.click()
|
||||
|
||||
// Check that the code was updated
|
||||
await u.waitForCmdReceive('extrude')
|
||||
// Unfortunately this indentation seems to matter for the test
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const distance = sqrt(20)
|
||||
const distance001 = ${KCL_DEFAULT_LENGTH}
|
||||
const 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 extrude001 = extrude(distance001, sketch001)`.replace(
|
||||
/(\r\n|\n|\r)/gm,
|
||||
''
|
||||
) // remove newlines
|
||||
)
|
||||
})
|
||||
|
||||
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' })
|
||||
const rectangleToolCommand = page.getByRole('option', {
|
||||
name: 'rectangle',
|
||||
})
|
||||
const rectangleToolButton = page.getByRole('button', {
|
||||
name: 'Corner rectangle',
|
||||
exact: true,
|
||||
})
|
||||
const lineToolCommand = page.getByRole('option', {
|
||||
name: 'Line',
|
||||
})
|
||||
const lineToolButton = page.getByRole('button', {
|
||||
name: 'Line',
|
||||
exact: true,
|
||||
})
|
||||
const arcToolCommand = page.getByRole('option', { name: 'Tangential Arc' })
|
||||
const arcToolButton = page.getByRole('button', {
|
||||
name: 'Tangential Arc',
|
||||
exact: true,
|
||||
})
|
||||
|
||||
// Start a sketch
|
||||
await sketchButton.click()
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
// Switch between sketch tools via the command bar
|
||||
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await cmdBarButton.click()
|
||||
await rectangleToolCommand.click()
|
||||
await expect(rectangleToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await cmdBarButton.click()
|
||||
await lineToolCommand.click()
|
||||
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// Click in the scene a couple times to draw a line
|
||||
// so tangential arc is valid
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.move(700, 300, { steps: 5 })
|
||||
await page.mouse.click(700, 300)
|
||||
|
||||
// switch to tangential arc via command bar
|
||||
await cmdBarButton.click()
|
||||
await arcToolCommand.click()
|
||||
await expect(arcToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
})
|
519
e2e/playwright/copilot-ghost-test.spec.ts
Normal file
@ -0,0 +1,519 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
test.describe('Copilot ghost text', () => {
|
||||
test.skip(true, 'Needs to get covered again')
|
||||
|
||||
test('completes code in empty file', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// We should be able to hit Tab to accept the completion.
|
||||
await page.keyboard.press('Tab')
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
|
||||
// Hit enter a few times.
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %) `
|
||||
)
|
||||
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.skip('copilot disabled in sketch mode no select plane', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
// Click sketch mode.
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
// Exit sketch mode.
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// We should be able to hit Tab to accept the completion.
|
||||
await page.keyboard.press('Tab')
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
})
|
||||
|
||||
test('copilot disabled in sketch mode after selecting plane', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
// Click sketch mode.
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
await page.waitForTimeout(700) // wait for animation
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ')`
|
||||
)
|
||||
|
||||
// Escape to exit the tool.
|
||||
await u.openDebugPanel()
|
||||
await u.closeDebugPanel()
|
||||
await page.keyboard.press('Escape')
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ')`
|
||||
)
|
||||
|
||||
// Escape again to exit sketch mode.
|
||||
await u.openDebugPanel()
|
||||
await u.closeDebugPanel()
|
||||
await page.keyboard.press('Escape')
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ')fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// We should be able to hit Tab to accept the completion.
|
||||
await page.keyboard.press('Tab')
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ')fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
|
||||
// Hit enter a few times.
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ')fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %) `
|
||||
)
|
||||
|
||||
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('ArrowUp 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 u.waitForAuthSkipAppStart()
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const 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('ArrowUp')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('ArrowDown 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 u.waitForAuthSkipAppStart()
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const 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('ArrowDown')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('ArrowLeft 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 u.waitForAuthSkipAppStart()
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const 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('ArrowLeft')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('ArrowRight 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 u.waitForAuthSkipAppStart()
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const 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('ArrowRight')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('Enter in code scoots it down', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const 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('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
})
|
||||
|
||||
test('Ctrl+shift+z 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 u.waitForAuthSkipAppStart()
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const 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.down(CtrlKey)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('Shift')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
test('Ctrl+z in code rejects the suggestion and undos the last code', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
await page.waitForTimeout(800)
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
await page.keyboard.type('{thing: "blah"}', { delay: 0 })
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`)
|
||||
|
||||
// We wanna make sure the code saves.
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
// Ctrl+z
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
// Ctrl+shift+z
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`)
|
||||
|
||||
// We wanna make sure the code saves.
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.locator('.cm-ghostText').first()).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`{thing: "blah"}fn cube = (pos, scale) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const part001 = cube([0,0], 20) |> close(%) |> extrude(20, %)`
|
||||
)
|
||||
await expect(page.locator('.cm-ghostText').first()).toHaveText(
|
||||
`fn cube = (pos, scale) => {`
|
||||
)
|
||||
|
||||
// Once for the enter.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
|
||||
// Once for the text.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
|
||||
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(``)
|
||||
})
|
||||
|
||||
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 u.waitForAuthSkipAppStart()
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const 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()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
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 u.waitForAuthSkipAppStart()
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const 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()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
|
||||
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 u.waitForAuthSkipAppStart()
|
||||
|
||||
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) => { const sg = startSketchOn('XY') |> startProfileAt(pos, %) |> line([0, scale], %) |> line([scale, 0], %) |> line([0, -scale], %) return sg}const 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()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
})
|
||||
})
|
867
e2e/playwright/editor-tests.spec.ts
Normal file
@ -0,0 +1,867 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
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()
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
// |> close(%)`)
|
||||
|
||||
// uncomment the code
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const 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', 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(`const 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(`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('fold gutters work', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
const fullCode = `const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
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.
|
||||
await page.locator('.cm-content').click()
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
const foldGutterFoldLine = page.locator('[title="Fold line"]')
|
||||
const foldGutterUnfoldLine = page.locator('[title="Unfold line"]')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(fullCode)
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Make sure we have a fold gutter
|
||||
await expect(foldGutterFoldLine).toBeVisible()
|
||||
await expect(foldGutterUnfoldLine).not.toBeVisible()
|
||||
|
||||
// Collapse the code
|
||||
await foldGutterFoldLine.click()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XY')… `
|
||||
)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(fullCode)
|
||||
await expect(foldGutterFoldLine).not.toBeVisible()
|
||||
await expect(foldGutterUnfoldLine.nth(1)).toBeVisible()
|
||||
|
||||
// Expand the code
|
||||
await foldGutterUnfoldLine.nth(1).click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(fullCode)
|
||||
|
||||
// Delete all the code.
|
||||
await page.locator('.cm-content').click()
|
||||
// Select all
|
||||
await page.keyboard.press('Control+A')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Meta+A')
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(fullCode)
|
||||
|
||||
await expect(foldGutterUnfoldLine).not.toBeVisible()
|
||||
await expect(foldGutterFoldLine).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('hover over functions shows function description', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
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.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// focus the editor
|
||||
await u.codeLocator.click()
|
||||
|
||||
// Hover over the startSketchOn function
|
||||
await page.getByText('startSketchOn').hover()
|
||||
await expect(page.locator('.hover-tooltip')).toBeVisible()
|
||||
await expect(
|
||||
page.getByText(
|
||||
'Start a new 2-dimensional sketch on a specific plane or face'
|
||||
)
|
||||
).toBeVisible()
|
||||
|
||||
// Hover over the line function
|
||||
await page.getByText('line').first().hover()
|
||||
await expect(page.locator('.hover-tooltip')).toBeVisible()
|
||||
await expect(page.getByText('Draw a line')).toBeVisible()
|
||||
})
|
||||
|
||||
test('if you use the format keyboard binding it formats your code', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
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.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// focus the editor
|
||||
await u.codeLocator.click()
|
||||
|
||||
// Hit alt+shift+f to format the code
|
||||
await page.keyboard.press('Alt+Shift+KeyF')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('if you write kcl with lint errors you get lints', 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-info')).not.toBeVisible()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('const my_snake_case_var = 5')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('const myCamelCaseVar = 5')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// press arrows to clear autocomplete
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
await page.keyboard.press('ArrowRight')
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-info')
|
||||
await expect(
|
||||
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||
).toBeVisible()
|
||||
|
||||
// select the line that's causing the error and delete it
|
||||
await page.getByText('const my_snake_case_var = 5').click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('Home')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
// wait for .cm-lint-marker-info not to be visible
|
||||
await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('if you fixup kcl errors you clear lints', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
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.getByText(' |> line([2.48, 2.44], %)').click()
|
||||
|
||||
await expect(
|
||||
page.locator('.cm-lint-marker-error').first()
|
||||
).not.toBeVisible()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
await page.keyboard.type(')')
|
||||
await expect(
|
||||
page.locator('.cm-lint-marker-error').first()
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('if you write invalid kcl you get inlined errors', 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()
|
||||
|
||||
/* add the following code to the editor ($ error is not a valid line)
|
||||
$ error
|
||||
const topAng = 30
|
||||
const bottomAng = 25
|
||||
*/
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('$ error')
|
||||
|
||||
// press arrows to clear autocomplete
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
await page.keyboard.press('ArrowRight')
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('const topAng = 30')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('const bottomAng = 25')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// error in guter
|
||||
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()
|
||||
|
||||
// select the line that's causing the error and delete it
|
||||
await page.getByText('$ error').click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('Home')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
// wait for .cm-lint-marker-error not to be visible
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// let's check we get an error when defining the same variable twice
|
||||
await page.getByText('const bottomAng = 25').click()
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type("// Let's define the same thing twice")
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('const topAng = 42')
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
await expect(
|
||||
page.locator('.cm-lint-marker.cm-lint-marker-error')
|
||||
).toBeVisible()
|
||||
|
||||
await page.locator('.cm-lint-marker.cm-lint-marker-error').hover()
|
||||
await expect(page.locator('.cm-diagnosticText').first()).toBeVisible()
|
||||
await expect(
|
||||
page.getByText('Cannot redefine `topAng`').first()
|
||||
).toBeVisible()
|
||||
|
||||
const secondTopAng = page.getByText('topAng').first()
|
||||
await secondTopAng?.dblclick()
|
||||
await page.keyboard.type('otherAng')
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const length = .750
|
||||
const width = 0.500
|
||||
const height = 0.500
|
||||
const dia = 4
|
||||
|
||||
fn squareHole = (l, w) => {
|
||||
const 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.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// Click on the bottom of the code editor to add a new line
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(`const extrusion = startSketchOn('XY')
|
||||
|> circle([0, 0], dia/2, %)
|
||||
|> hole(squareHole(length, width, height), %)
|
||||
|> extrude(height, %)`)
|
||||
|
||||
// error in gutter
|
||||
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
|
||||
await page.hover('.cm-lint-marker-error:first-child')
|
||||
await expect(
|
||||
page.getByText('Expected 2 arguments, got 3').first()
|
||||
).toBeVisible()
|
||||
|
||||
// Make sure there are two diagnostics
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
|
||||
})
|
||||
test('if your kcl gets an error from the engine it is inlined', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const box = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %, $revolveAxis)
|
||||
|> close(%)
|
||||
|> extrude(10, %)
|
||||
|
||||
const sketch001 = startSketchOn(box, revolveAxis)
|
||||
|> startProfileAt([5, 10], %)
|
||||
|> line([0, -10], %)
|
||||
|> line([2, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> close(%)
|
||||
|> revolve({
|
||||
axis: revolveAxis,
|
||||
angle: 90
|
||||
}, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await page.goto('/')
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
const searchText =
|
||||
'sketch profile must lie entirely on one side of the revolution axis'
|
||||
await expect(page.getByText(searchText)).toBeVisible()
|
||||
})
|
||||
test.describe('Autocomplete works', () => {
|
||||
test('with enter/click to accept the completion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// this test might be brittle as we add and remove functions
|
||||
// but should also be easy to update.
|
||||
// tests clicking on an option, selection the first option
|
||||
// and arrowing down to an option
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('const sketch001 = start')
|
||||
|
||||
// expect there to be six auto complete options
|
||||
await expect(page.locator('.cm-completionLabel')).toHaveCount(8)
|
||||
// this makes sure we can accept a completion with click
|
||||
await page.getByText('startSketchOn').click()
|
||||
await page.keyboard.type("'XZ'")
|
||||
await page.keyboard.press('Tab')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(' |> startProfi')
|
||||
// expect there be a single auto complete option that we can just hit enter on
|
||||
await expect(page.locator('.cm-completionLabel')).toBeVisible()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter') // accepting the auto complete, not a new line
|
||||
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.type('12')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.type(' |> lin')
|
||||
|
||||
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible()
|
||||
await page.waitForTimeout(100)
|
||||
// press arrow down twice then enter to accept xLine
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Enter')
|
||||
// finish line with comment
|
||||
await page.keyboard.type('5')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
|
||||
await page.keyboard.type(' // ')
|
||||
// Since we need to parse the ast to know we are in a comment we gotta hang tight.
|
||||
await page.waitForTimeout(700)
|
||||
await page.keyboard.type('lin ')
|
||||
await page.waitForTimeout(200)
|
||||
// there shouldn't be any auto complete options for 'lin' in the comment
|
||||
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
})
|
||||
|
||||
test('with tab to accept the completion', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// this test might be brittle as we add and remove functions
|
||||
// but should also be easy to update.
|
||||
// tests clicking on an option, selection the first option
|
||||
// and arrowing down to an option
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('const sketch001 = startSketchO')
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// Make sure just hitting tab will take the only one left
|
||||
await expect(page.locator('.cm-completionLabel')).toHaveCount(1)
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.type("'XZ'")
|
||||
await page.keyboard.press('Tab')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(' |> startProfi')
|
||||
// expect there be a single auto complete option that we can just hit enter on
|
||||
await expect(page.locator('.cm-completionLabel')).toBeVisible()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab') // accepting the auto complete, not a new line
|
||||
|
||||
await page.keyboard.press('Tab')
|
||||
await page.keyboard.type('12')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.type(' |> lin')
|
||||
|
||||
await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible()
|
||||
await page.waitForTimeout(100)
|
||||
// press arrow down twice then tab to accept xLine
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('Tab')
|
||||
// finish line with comment
|
||||
await page.keyboard.type('5')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
|
||||
await page.keyboard.type(' // ')
|
||||
// Since we need to parse the ast to know we are in a comment we gotta hang tight.
|
||||
await page.waitForTimeout(700)
|
||||
await page.keyboard.type('lin ')
|
||||
await page.waitForTimeout(200)
|
||||
// there shouldn't be any auto complete options for 'lin' in the comment
|
||||
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
})
|
||||
})
|
||||
test('Can undo a click and point extrude with ctrl+z', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [665, 458]
|
||||
|
||||
const dragPX = 40
|
||||
|
||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
|
||||
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ') |> startProfileAt([4.61, -14.01], %) |> line([12.73, -0.09], %) |> tangentialArcTo([24.95, -5.38], %) |> close(%)const extrude001 = extrude(5, sketch001)`
|
||||
)
|
||||
|
||||
// Now hit undo
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
// failing for the same reason as "Can edit a sketch that has been extruded in the same pipe"
|
||||
// please fix together
|
||||
test.fixme('Can undo a sketch modification with ctrl+z', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [665, 458]
|
||||
|
||||
const dragPX = 40
|
||||
|
||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).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(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: startPX[0], y: startPX[1] },
|
||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
// drag line handle
|
||||
// we wait so it saves the code
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
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()
|
||||
|
||||
// we wait so it saves the code
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
// drag tangentialArcTo handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 },
|
||||
targetPosition: {
|
||||
x: tangentEnd.x + dragPX,
|
||||
y: tangentEnd.y + dragPX,
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -16.82], %)
|
||||
|> line([15.4, -2.74], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> line([2.65, -2.69], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|
||||
// Hit undo
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -16.82], %)
|
||||
|> line([15.4, -2.74], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -16.82], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
})
|
||||
})
|
341
e2e/playwright/onboarding-tests.spec.ts
Normal file
@ -0,0 +1,341 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown } 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'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
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', 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)
|
||||
},
|
||||
{ settingsKey: TEST_SETTINGS_KEY }
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
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('Code resets after confirmation', async ({ page }) => {
|
||||
const initialCode = `const sketch001 = startSketchOn('XZ')`
|
||||
|
||||
// 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)
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// Replay the onboarding
|
||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||
const replayButton = page.getByRole('button', { name: 'Replay onboarding' })
|
||||
await expect(replayButton).toBeVisible()
|
||||
await replayButton.click()
|
||||
|
||||
// Ensure we see the warning, and that the code has not yet updated
|
||||
await expect(
|
||||
page.getByText('Replaying onboarding resets your code')
|
||||
).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toHaveText(initialCode)
|
||||
|
||||
const nextButton = page.getByTestId('onboarding-next')
|
||||
await expect(nextButton).toBeVisible()
|
||||
await nextButton.click()
|
||||
|
||||
// Ensure we see the introduction and that the code has been reset
|
||||
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||
|
||||
// Ensure we persisted the code to local storage.
|
||||
// Playwright's addInitScript method unfortunately will reset
|
||||
// this code if we try reloading the page as a test,
|
||||
// so this is our best way to test persistence afaik.
|
||||
expect(
|
||||
await page.evaluate(() => {
|
||||
return localStorage.getItem('persistCode')
|
||||
})
|
||||
).toContain('// Shelf Bracket')
|
||||
})
|
||||
|
||||
test('Click through each onboarding step', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Override beforeEach test setup
|
||||
await page.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
// Give no initial code, so that the onboarding start is shown immediately
|
||||
localStorage.setItem('persistCode', '')
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }),
|
||||
}
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// 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', 'const sigmaAllow = 15000')
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
},
|
||||
{
|
||||
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)
|
||||
}
|
||||
})
|
||||
})
|
226
e2e/playwright/regression-tests.spec.ts
Normal file
@ -0,0 +1,226 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
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 ({ 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 page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch2 = startSketchOn("XY")
|
||||
const sketch001 = startSketchAt([-0, -0])
|
||||
|> line([0, 0], %)
|
||||
|> line([-4.84, -5.29], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
// this is a cryptic error message, fact that all the lines are co-linear from the `line([0,0])` is the issue why
|
||||
// the close doesn't work
|
||||
// when https://github.com/KittyCAD/modeling-app/issues/3268 is closed
|
||||
// this test will need updating
|
||||
const crypticErrorText = `ApiError`
|
||||
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
||||
})
|
||||
test('executes on load', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([-6.95, 4.98], %)
|
||||
|> line([25.1, 0.41], %)
|
||||
|> line([0.73, -14.93], %)
|
||||
|> line([-23.44, 0.52], %)`
|
||||
)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// expand variables section
|
||||
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||
await variablesTabButton.click()
|
||||
|
||||
// can find sketch001 in the variables summary (pretty-json-container, makes sure we're not looking in the code editor)
|
||||
// sketch001 only shows up in the variables summary if it's been executed
|
||||
await page.waitForFunction(() => {
|
||||
const variablesElement = document.querySelector(
|
||||
'.pretty-json-container'
|
||||
) as HTMLDivElement
|
||||
return variablesElement.innerHTML.includes('sketch001')
|
||||
})
|
||||
await expect(
|
||||
page.locator('.pretty-json-container >> text=sketch001')
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('re-executes', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', `const myVar = 5`)
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const variablesTabButton = page.getByTestId('variables-pane-button')
|
||||
await variablesTabButton.click()
|
||||
// expect to see "myVar:5"
|
||||
await expect(
|
||||
page.locator('.pretty-json-container >> text=myVar:5')
|
||||
).toBeVisible()
|
||||
|
||||
// change 5 to 67
|
||||
await page.getByText('const myVar').click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.type('67')
|
||||
|
||||
await expect(
|
||||
page.locator('.pretty-json-container >> text=myVar:67')
|
||||
).toBeVisible()
|
||||
})
|
||||
test('ProgramMemory can be serialised', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const 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
|
||||
}, %)`
|
||||
)
|
||||
})
|
||||
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 u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
const forbiddenMessages = ['cannot serialize tagged newtype variant']
|
||||
forbiddenMessages.forEach((forbiddenMessage) => {
|
||||
messages.forEach((message) => {
|
||||
expect(message).not.toContain(forbiddenMessage)
|
||||
})
|
||||
})
|
||||
})
|
||||
test('ensure the Zoo logo is not a link in browser app', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
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', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|>
|
||||
|
||||
const example = extrude(5, exampleSketch)
|
||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||
)
|
||||
})
|
||||
|
||||
await expect(async () => {
|
||||
await page.goto('/')
|
||||
await u.waitForPageLoad()
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||
timeout: 1_000,
|
||||
})
|
||||
await page.waitForTimeout(200)
|
||||
// expect it still to be there (sometimes it just clears for a bit?)
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||
timeout: 1_000,
|
||||
})
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||
|
||||
// Okay execution finished, let's start editing text below the error.
|
||||
await u.codeLocator.click()
|
||||
// Go to the end of the editor
|
||||
// This bug happens when there is a diagnostic in the editor and you try to
|
||||
// edit text below it.
|
||||
// Or delete a huge chunk of text and then try to edit below it.
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('thing: "blah"', { delay: 100 })
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|
||||
thing: "blah"`)
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
})
|
||||
})
|
959
e2e/playwright/sketch-tests.spec.ts
Normal file
@ -0,0 +1,959 @@
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
|
||||
import {
|
||||
getMovementUtils,
|
||||
getUtils,
|
||||
PERSIST_MODELING_CONTEXT,
|
||||
setup,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
import { uuidv4, roundOff } from 'lib/utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
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,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
const selectionsSnippets = {
|
||||
startProfileAt1:
|
||||
'|> startProfileAt([-width / 4 + screwRadius, height / 2], %)',
|
||||
startProfileAt2: '|> startProfileAt([-width / 2, 0], %)',
|
||||
startProfileAt3: '|> startProfileAt([0, thickness], %)',
|
||||
}
|
||||
await context.addInitScript(
|
||||
async ({ startProfileAt1, startProfileAt2, startProfileAt3 }: any) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`
|
||||
const width = 20
|
||||
const height = 10
|
||||
const thickness = 5
|
||||
const screwRadius = 3
|
||||
const wireRadius = 2
|
||||
const wireOffset = 0.5
|
||||
|
||||
const screwHole = startSketchOn('XY')
|
||||
${startProfileAt1}
|
||||
|> arc({
|
||||
radius: screwRadius,
|
||||
angle_start: 0,
|
||||
angle_end: 360
|
||||
}, %)
|
||||
|
||||
const part001 = startSketchOn('XY')
|
||||
${startProfileAt2}
|
||||
|> xLine(width * .5, %)
|
||||
|> yLine(height, %)
|
||||
|> xLine(-width * .5, %)
|
||||
|> close(%)
|
||||
|> hole(screwHole, %)
|
||||
|> extrude(thickness, %)
|
||||
|
||||
const 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.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.getByText(selectionsSnippets.startProfileAt1).click()
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
await page.getByText(selectionsSnippets.startProfileAt2).click()
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
await page.getByText(selectionsSnippets.startProfileAt3).click()
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
})
|
||||
test('Can delete most of a sketch and the line tool will still work', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)`
|
||||
)
|
||||
})
|
||||
|
||||
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' })
|
||||
).toBeEnabled({ timeout: 1000 })
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
|
||||
await page.waitForTimeout(600) // wait for animation
|
||||
|
||||
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('Home')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([0.31, 16.47], %)`
|
||||
)
|
||||
})
|
||||
test('Can exit selection of face', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem('persistCode', ``)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
await expect(page.getByText('select a plane or face')).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
})
|
||||
test.describe('Can edit segments by dragging their handles', () => {
|
||||
const doEditSegmentsByDraggingHandle = async (
|
||||
page: Page,
|
||||
openPanes: string[]
|
||||
) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// If we have the code pane open, we should see the code.
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)`)
|
||||
} else {
|
||||
// Ensure we don't see the code.
|
||||
await expect(u.codeLocator).not.toBeVisible()
|
||||
}
|
||||
|
||||
const startPX = [665, 458]
|
||||
|
||||
const dragPX = 30
|
||||
let prevContent = ''
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||
} else {
|
||||
// Wait for the render.
|
||||
await page.waitForTimeout(1000)
|
||||
// Select the sketch
|
||||
await page.mouse.click(700, 370)
|
||||
}
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
if (openPanes.includes('code')) {
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
|
||||
const step5 = { steps: 5 }
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
|
||||
// drag line handle
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.mouse.move(lineEnd.x - 5, lineEnd.y)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
|
||||
// drag tangentialArcTo handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(tangentEnd.x + dragPX, tangentEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
}
|
||||
|
||||
// Open the code pane
|
||||
await u.openKclCodePanel()
|
||||
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([6.44, -12.07], %)
|
||||
|> line([14.72, 1.97], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> line([1.97, 2.06], %)
|
||||
|> close(%)`)
|
||||
}
|
||||
test('code pane open at start-handles', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'store',
|
||||
JSON.stringify({
|
||||
state: {
|
||||
openPanes: ['code'],
|
||||
},
|
||||
version: 0,
|
||||
})
|
||||
)
|
||||
})
|
||||
await doEditSegmentsByDraggingHandle(page, ['code'])
|
||||
})
|
||||
|
||||
test('code pane closed at start-handles', async ({ page }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async (persistModelingContext) => {
|
||||
localStorage.setItem(
|
||||
persistModelingContext,
|
||||
JSON.stringify({ openPanes: [] })
|
||||
)
|
||||
}, PERSIST_MODELING_CONTEXT)
|
||||
await doEditSegmentsByDraggingHandle(page, [])
|
||||
})
|
||||
})
|
||||
|
||||
// failing for the same reason as "Can undo a sketch modification with ctrl+z"
|
||||
// please fix together
|
||||
test.fixme(
|
||||
'Can edit a sketch that has been extruded in the same pipe',
|
||||
async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [665, 458]
|
||||
|
||||
const dragPX = 40
|
||||
|
||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).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(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: startPX[0], y: startPX[1] },
|
||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
// drag line handle
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
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()
|
||||
|
||||
// drag tangentialArcTo handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 },
|
||||
targetPosition: {
|
||||
x: tangentEnd.x + dragPX,
|
||||
y: tangentEnd.y + dragPX,
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -16.82], %)
|
||||
|> line([15.4, -2.74], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> line([2.65, -2.69], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
}
|
||||
)
|
||||
|
||||
test('Can edit a sketch that has been revolved in the same pipe', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> revolve({ axis: "X",}, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [665, 458]
|
||||
|
||||
const dragPX = 30
|
||||
|
||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
const step5 = { steps: 5 }
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
// drag line handle
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.mouse.move(lineEnd.x - 5, lineEnd.y)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
// drag tangentialArcTo handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(tangentEnd.x + dragPX, tangentEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const 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" }, %)`)
|
||||
})
|
||||
test('Can add multiple sketches', async ({ page }) => {
|
||||
test.skip(process.platform === 'darwin', 'Can add multiple sketches')
|
||||
const u = await getUtils(page)
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||
const { toSU, click00r } = getMovementUtils({ center, page })
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||
200
|
||||
)
|
||||
|
||||
let codeStr = "const sketch001 = startSketchOn('XY')"
|
||||
|
||||
await page.mouse.click(center.x, viewportSize.height * 0.55)
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
await u.closeDebugPanel()
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
await click00r(0, 0)
|
||||
codeStr += ` |> startProfileAt(${toSU([0, 0])}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(50, 0)
|
||||
await page.waitForTimeout(100)
|
||||
codeStr += ` |> line(${toSU([50, 0])}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(0, 50)
|
||||
codeStr += ` |> line(${toSU([0, 50])}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(-50, 0)
|
||||
codeStr += ` |> line(${toSU([-50, 0])}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
// exit the sketch, reset relative clicker
|
||||
click00r(undefined, undefined)
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await page.waitForTimeout(250)
|
||||
await u.clearCommandLogs()
|
||||
|
||||
// start a new sketch
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
// when exiting the sketch above the camera is still looking down at XY,
|
||||
// so selecting the plane again is a bit easier.
|
||||
await page.mouse.click(center.x + 200, center.y + 100)
|
||||
await page.waitForTimeout(600) // TODO detect animation ending, or disable animation
|
||||
codeStr += "const sketch002 = startSketchOn('XY')"
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await click00r(30, 0)
|
||||
codeStr += ` |> startProfileAt([1.53, 0], %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(30, 0)
|
||||
codeStr += ` |> line([1.53, 0], %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(0, 30)
|
||||
codeStr += ` |> line([0, -1.53], %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(-30, 0)
|
||||
codeStr += ` |> line([-1.53, 0], %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(undefined, undefined)
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.updateCamPosition([100, 100, 100])
|
||||
await u.clearCommandLogs()
|
||||
})
|
||||
test.describe('Snap to close works (at any scale)', () => {
|
||||
const doSnapAtDifferentScales = async (
|
||||
page: any,
|
||||
camPos: [number, number, number],
|
||||
scale = 1
|
||||
) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const code = `const sketch001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|
||||
|> line([${roundOff(scale * 139.19)}, 0], %)
|
||||
|> line([0, -${roundOff(scale * 139.2)}], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.updateCamPosition(camPos)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.mouse.move(0, 0)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.move(700, 200, { steps: 10 })
|
||||
await page.mouse.click(700, 200, { delay: 200 })
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('-XZ')`
|
||||
)
|
||||
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
const pointA = [700, 200]
|
||||
const pointB = [900, 200]
|
||||
const pointC = [900, 400]
|
||||
|
||||
// draw three lines
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.move(pointA[0], pointA[1], { steps: 10 })
|
||||
await page.mouse.click(pointA[0], pointA[1], { delay: 200 })
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.move(pointB[0], pointB[1], { steps: 10 })
|
||||
await page.mouse.click(pointB[0], pointB[1], { delay: 200 })
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.move(pointC[0], pointC[1], { steps: 10 })
|
||||
await page.mouse.click(pointC[0], pointC[1], { delay: 200 })
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.move(pointA[0] - 12, pointA[1] + 12, { steps: 10 })
|
||||
const pointNotQuiteA = [pointA[0] - 7, pointA[1] + 7]
|
||||
await page.mouse.move(pointNotQuiteA[0], pointNotQuiteA[1], { steps: 10 })
|
||||
|
||||
await page.mouse.click(pointNotQuiteA[0], pointNotQuiteA[1], {
|
||||
delay: 200,
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
// Assert the tool was unequipped
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Line', exact: true })
|
||||
).not.toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.removeCurrentCode()
|
||||
}
|
||||
test('[0, 100, 100]', async ({ page }) => {
|
||||
await doSnapAtDifferentScales(page, [0, 100, 100], 0.01)
|
||||
})
|
||||
|
||||
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,
|
||||
}) => {
|
||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const 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(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// click "line([1.32, 0.38], %)"
|
||||
await page.getByText(`line([1.32, 0.38], %)`).click()
|
||||
await page.waitForTimeout(100)
|
||||
// click edit sketch
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(600) // wait for animation
|
||||
|
||||
// exit sketch
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
// expect extrude button to be enabled
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Extrude' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
// click extrude
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
|
||||
// sketch selection should already have been made. "Selection: 1 face" only show up when the selection has been made already
|
||||
// otherwise the cmdbar would be waiting for a selection.
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'selection : 1 face', exact: false })
|
||||
).toBeVisible()
|
||||
})
|
||||
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',
|
||||
`const 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(%)
|
||||
const extrude001 = extrude(5, sketch001)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
await page.mouse.click(622, 355)
|
||||
|
||||
await page.waitForTimeout(800)
|
||||
await page.getByText(`END')`).click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type(' |>', { delay: 100 })
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
await expect((await u.codeLocator.innerText()).replace(/\s/g, '')).toBe(
|
||||
`const 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(%)
|
||||
const extrude001 = extrude(5, sketch001)
|
||||
const sketch002 = startSketchOn(extrude001, 'END')
|
||||
|>
|
||||
`.replace(/\s/g, '')
|
||||
)
|
||||
})
|
||||
test('empty-scene default-planes act as expected', async ({
|
||||
page,
|
||||
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
|
||||
* 2) The planes don't changes color when hovered initially
|
||||
* 3) Putting something in the scene makes the planes hidden
|
||||
* 4) Removing everything from the scene shows the plans again
|
||||
* 3) Once "start sketch" is click, the planes do respond to hovers
|
||||
* 4) Selecting a plan works as expected, i.e. sketch mode
|
||||
* 5) Reloading the scene with something already in the scene means the planes are hidden
|
||||
*/
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||
const unHoveredColor: [number, number, number] = [47, 47, 93]
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await u.codeLocator.fill(`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
`)
|
||||
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
const noPlanesColor: [number, number, number] = [30, 30, 30]
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
|
||||
).toBeLessThan(3)
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await u.removeCurrentCode()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(XYPlanePoint, unHoveredColor), {
|
||||
timeout: 5_000,
|
||||
})
|
||||
.toBeLessThan(8)
|
||||
|
||||
// click start Sketch
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
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
|
||||
.poll(() => u.getGreatestPixDiff(XYPlanePoint, hoveredColor))
|
||||
.toBeLessThan(8)
|
||||
|
||||
await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
await page.mouse.click(XYPlanePoint.x + 50, XYPlanePoint.y + 50)
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([11.8, 9.09], %)
|
||||
|> line([3.39, -3.39], %)
|
||||
`)
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> 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"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// expect there to be no planes on load since there's something in the scene
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
|
||||
).toBeLessThan(3)
|
||||
})
|
||||
})
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 59 KiB |
@ -86,3 +86,285 @@ export const TEST_CODE_GIZMO = `const part001 = startSketchOn('XZ')
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
`
|
||||
|
||||
export const TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW = `const width = 50.8
|
||||
const height = 30
|
||||
const thickness = 2
|
||||
const keychainHoleSize = 3
|
||||
|
||||
const keychain = 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, %)
|
||||
|
||||
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, %)
|
||||
|
||||
const box = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %, $revolveAxis)
|
||||
|> close(%)
|
||||
|> extrude(10, %)
|
||||
|
||||
const sketch001 = startSketchOn(box, revolveAxis)
|
||||
|> startProfileAt([5, 10], %)
|
||||
|> line([0, -10], %)
|
||||
|> line([2, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> close(%)
|
||||
|> revolve({
|
||||
axis: revolveAxis,
|
||||
angle: 90
|
||||
}, %)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
svg(startSketchOn(keychain, 'end'), [-33, 32], -thickness)
|
||||
|
||||
startSketchOn(keychain, 'end')
|
||||
|> circle([
|
||||
width / 2,
|
||||
height - (keychainHoleSize + 1.5)
|
||||
], keychainHoleSize, %)
|
||||
|> extrude(-thickness, %)`
|
||||
|
237
e2e/playwright/test-network-and-connection-issues.spec.ts
Normal file
@ -0,0 +1,237 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { commonPoints, getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
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,
|
||||
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.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
|
||||
// This is how we wait until the stream is online
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
|
||||
const networkWidget = page.locator('[data-testid="network-toggle"]')
|
||||
await expect(networkWidget).toBeVisible()
|
||||
await networkWidget.hover()
|
||||
|
||||
const networkPopover = page.locator('[data-testid="network-popover"]')
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
|
||||
// (First check) Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).toBeVisible()
|
||||
|
||||
// Click off the modal.
|
||||
await page.mouse.click(100, 100)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
|
||||
// Turn off the network
|
||||
await u.emulateNetworkConditions({
|
||||
offline: true,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Offline')
|
||||
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).toBeVisible()
|
||||
|
||||
// Click off the modal.
|
||||
await page.mouse.click(0, 0)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
|
||||
// Turn back on the network
|
||||
await u.emulateNetworkConditions({
|
||||
offline: false,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
|
||||
// (Second check) expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
})
|
||||
|
||||
test('Engine disconnect & reconnect in sketch mode', async ({
|
||||
page,
|
||||
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.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
|
||||
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()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ')`
|
||||
)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)`)
|
||||
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
|
||||
// simulate network down
|
||||
await u.emulateNetworkConditions({
|
||||
offline: true,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Offline')
|
||||
|
||||
// Ensure we are not in sketch mode
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
// simulate network up
|
||||
await u.emulateNetworkConditions({
|
||||
offline: false,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
// Wait for the app to be ready for use
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
|
||||
|
||||
// Click off the code pane.
|
||||
await page.mouse.click(100, 100)
|
||||
|
||||
// select a line
|
||||
await page.getByText(`startProfileAt(${commonPoints.startAt}, %)`).click()
|
||||
|
||||
// enter sketch again
|
||||
await u.doAndWaitForCmd(
|
||||
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
||||
'default_camera_get_settings'
|
||||
)
|
||||
await page.waitForTimeout(150)
|
||||
|
||||
// Click the line tool
|
||||
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||
|
||||
await page.waitForTimeout(150)
|
||||
|
||||
// Ensure we can continue sketching
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([-8.84, 8.75], %)`)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([-8.84, 8.75], %)
|
||||
|> line([-5.6, 0], %)`)
|
||||
|
||||
// Unequip line tool
|
||||
await page.keyboard.press('Escape')
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Line', exact: true })
|
||||
).not.toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// Exit sketch
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
})
|
@ -1,5 +1,11 @@
|
||||
import { expect, Page, Download } from '@playwright/test'
|
||||
import { EngineCommand } from 'lang/std/artifactMap'
|
||||
import {
|
||||
expect,
|
||||
Page,
|
||||
Download,
|
||||
TestInfo,
|
||||
BrowserContext,
|
||||
} from '@playwright/test'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import os from 'os'
|
||||
import fsp from 'fs/promises'
|
||||
import pixelMatch from 'pixelmatch'
|
||||
@ -7,6 +13,10 @@ import { PNG } from 'pngjs'
|
||||
import { Protocol } from 'playwright-core/types/protocol'
|
||||
import type { Models } from '@kittycad/lib'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import waitOn from 'wait-on'
|
||||
import { secrets } from './secrets'
|
||||
import { TEST_SETTINGS_KEY, TEST_SETTINGS } from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
|
||||
type TestColor = [number, number, number]
|
||||
export const TEST_COLORS = {
|
||||
@ -15,6 +25,33 @@ export const TEST_COLORS = {
|
||||
BLUE: [0, 0, 255] as TestColor,
|
||||
} as const
|
||||
|
||||
export const PERSIST_MODELING_CONTEXT = 'persistModelingContext'
|
||||
|
||||
export const deg = (Math.PI * 2) / 360
|
||||
|
||||
export const commonPoints = {
|
||||
startAt: '[7.19, -9.7]',
|
||||
num1: 7.25,
|
||||
num2: 14.44,
|
||||
}
|
||||
|
||||
async function waitForPageLoadWithRetry(page: Page) {
|
||||
await expect(async () => {
|
||||
await page.goto('/')
|
||||
const errorMessage = 'App failed to load - 🔃 Retrying ...'
|
||||
await expect(page.getByTestId('loading'), errorMessage).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' }),
|
||||
errorMessage
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
}).toPass({ timeout: 70_000, intervals: [1_000] })
|
||||
}
|
||||
|
||||
async function waitForPageLoad(page: Page) {
|
||||
// wait for all spinners to be gone
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
@ -209,18 +246,24 @@ export const getMovementUtils = (opts: any) => {
|
||||
}
|
||||
|
||||
async function waitForAuthAndLsp(page: Page) {
|
||||
const waitForLspPromise = page.waitForEvent('console', async (message) => {
|
||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||
// but that doesn't seem to make it to the console for macos/safari :(
|
||||
if (message.text().includes('start kcl lsp')) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
const waitForLspPromise = page.waitForEvent('console', {
|
||||
predicate: async (message) => {
|
||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||
// but that doesn't seem to make it to the console for macos/safari :(
|
||||
if (message.text().includes('start kcl lsp')) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
timeout: 45_000,
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await waitForPageLoad(page)
|
||||
if (process.env.CI) {
|
||||
await waitForPageLoadWithRetry(page)
|
||||
} else {
|
||||
await page.goto('/')
|
||||
await waitForPageLoad(page)
|
||||
}
|
||||
|
||||
return waitForLspPromise
|
||||
}
|
||||
@ -234,6 +277,7 @@ export async function getUtils(page: Page) {
|
||||
return {
|
||||
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||
waitForPageLoad: () => waitForPageLoad(page),
|
||||
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
|
||||
removeCurrentCode: () => removeCurrentCode(page),
|
||||
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
||||
updateCamPosition: async (xyz: [number, number, number]) => {
|
||||
@ -542,3 +586,46 @@ export const doExport = async (
|
||||
* Gets the appropriate modifier key for the platform.
|
||||
*/
|
||||
export const metaModifier = os.platform() === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
export async function tearDown(page: Page, testInfo: TestInfo) {
|
||||
if (testInfo.status === 'skipped') return
|
||||
if (testInfo.status === 'failed') return
|
||||
|
||||
const u = await getUtils(page)
|
||||
// Kill the network so shutdown happens properly
|
||||
await u.emulateNetworkConditions({
|
||||
offline: true,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
export async function setup(context: BrowserContext, page: Page) {
|
||||
// wait for Vite preview server to be up
|
||||
await waitOn({
|
||||
resources: ['tcp:3000'],
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
await context.addInitScript(
|
||||
async ({ token, settingsKey, settings }) => {
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||
localStorage.setItem('persistCode', ``)
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
localStorage.setItem('playwright', 'true')
|
||||
},
|
||||
{
|
||||
token: secrets.token,
|
||||
settingsKey: TEST_SETTINGS_KEY,
|
||||
settings: TOML.stringify({ settings: TEST_SETTINGS }),
|
||||
}
|
||||
)
|
||||
// kill animations, speeds up tests and reduced flakiness
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
}
|
||||
|
339
e2e/playwright/testing-camera-movement.spec.ts
Normal file
@ -0,0 +1,339 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Testing Camera Movement', () => {
|
||||
test('Can moving camera', async ({ page, context }) => {
|
||||
test.skip(process.platform === 'darwin', 'Can moving camera')
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.closeKclCodePanel()
|
||||
|
||||
const camPos: [number, number, number] = [0, 85, 85]
|
||||
const bakeInRetries = async (
|
||||
mouseActions: any,
|
||||
xyz: [number, number, number],
|
||||
cnt = 0
|
||||
) => {
|
||||
// hack that we're implemented our own retry instead of using retries built into playwright.
|
||||
// however each of these camera drags can be flaky, because of udp
|
||||
// and so putting them together means only one needs to fail to make this test extra flaky.
|
||||
// this way we can retry within the test
|
||||
// We could break them out into separate tests, but the longest past of the test is waiting
|
||||
// for the stream to start, so it can be good to bundle related things together.
|
||||
|
||||
const camCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
vantage: { x: camPos[0], y: camPos[1], z: camPos[2] },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
}
|
||||
const updateCamCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
}
|
||||
await u.sendCustomCmd(camCommand)
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd(updateCamCommand)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// rotate
|
||||
await u.closeDebugPanel()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
// const yo = page.getByTestId('cam-x-position').inputValue()
|
||||
|
||||
await u.doAndWaitForImageDiff(async () => {
|
||||
await mouseActions()
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.waitForTimeout(100)
|
||||
}, 300)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByTestId('cam-x-position').isVisible()
|
||||
|
||||
const vals = await Promise.all([
|
||||
page.getByTestId('cam-x-position').inputValue(),
|
||||
page.getByTestId('cam-y-position').inputValue(),
|
||||
page.getByTestId('cam-z-position').inputValue(),
|
||||
])
|
||||
const xError = Math.abs(Number(vals[0]) + xyz[0])
|
||||
const yError = Math.abs(Number(vals[1]) + xyz[1])
|
||||
const zError = Math.abs(Number(vals[2]) + xyz[2])
|
||||
|
||||
let shouldRetry = false
|
||||
|
||||
if (xError > 5 || yError > 5 || zError > 5) {
|
||||
if (cnt > 2) {
|
||||
console.log('xVal', vals[0], 'xError', xError)
|
||||
console.log('yVal', vals[1], 'yError', yError)
|
||||
console.log('zVal', vals[2], 'zError', zError)
|
||||
|
||||
throw new Error('Camera position not as expected')
|
||||
}
|
||||
shouldRetry = true
|
||||
}
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
if (shouldRetry) await bakeInRetries(mouseActions, xyz, cnt + 1)
|
||||
}
|
||||
await bakeInRetries(async () => {
|
||||
await page.mouse.move(700, 200)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(600, 303)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
}, [4, -10.5, -120])
|
||||
|
||||
await bakeInRetries(async () => {
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.move(600, 200)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(700, 200, { steps: 2 })
|
||||
await page.mouse.up({ button: 'right' })
|
||||
await page.keyboard.up('Shift')
|
||||
}, [-19, -85, -85])
|
||||
|
||||
const camCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
vantage: { x: camPos[0], y: camPos[1], z: camPos[2] },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
}
|
||||
const updateCamCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
}
|
||||
await u.sendCustomCmd(camCommand)
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd(updateCamCommand)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// zoom
|
||||
await u.doAndWaitForImageDiff(async () => {
|
||||
await page.keyboard.down('Control')
|
||||
await page.mouse.move(700, 400)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(700, 300)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
await u.openDebugPanel()
|
||||
await page.waitForTimeout(300)
|
||||
await u.clearCommandLogs()
|
||||
|
||||
await u.closeDebugPanel()
|
||||
}, 300)
|
||||
|
||||
// zoom with scroll
|
||||
await u.openAndClearDebugPanel()
|
||||
// TODO, it appears we don't get the cam setting back from the engine when the interaction is zoom into `backInRetries` once the information is sent back on zoom
|
||||
// await expect(Math.abs(Number(await page.getByTestId('cam-x-position').inputValue()) + 12)).toBeLessThan(1.5)
|
||||
// await expect(Math.abs(Number(await page.getByTestId('cam-y-position').inputValue()) - 85)).toBeLessThan(1.5)
|
||||
// await expect(Math.abs(Number(await page.getByTestId('cam-z-position').inputValue()) - 85)).toBeLessThan(1.5)
|
||||
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
|
||||
await bakeInRetries(async () => {
|
||||
await page.mouse.move(700, 400)
|
||||
await page.mouse.wheel(0, -100)
|
||||
}, [0, -85, -85])
|
||||
})
|
||||
|
||||
test('Zoom should be consistent when exiting or entering sketches', async ({
|
||||
page,
|
||||
}) => {
|
||||
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
||||
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
||||
// than again for sketching
|
||||
|
||||
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 325)
|
||||
|
||||
let code = `const sketch001 = startSketchOn('XY')`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
// move the camera slightly
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.move(700, 300)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(800, 200)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
let y = 350,
|
||||
x = 948
|
||||
|
||||
await u.canvasLocator.click({ position: { x: 783, y } })
|
||||
code += `\n |> startProfileAt([8.12, -12.98], %)`
|
||||
// await expect(u.codeLocator).toHaveText(code)
|
||||
await u.canvasLocator.click({ position: { x, y } })
|
||||
code += `\n |> line([11.18, 0], %)`
|
||||
// await expect(u.codeLocator).toHaveText(code)
|
||||
await u.canvasLocator.click({ position: { x, y: 275 } })
|
||||
code += `\n |> line([0, 6.99], %)`
|
||||
// await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
// click the line button
|
||||
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||
|
||||
const hoverOverNothing = async () => {
|
||||
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
|
||||
await page.mouse.move(700, 325)
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
}
|
||||
|
||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||
|
||||
await page.waitForTimeout(200)
|
||||
// hover over horizontal line
|
||||
await u.canvasLocator.hover({ position: { x: 800, y } })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await hoverOverNothing()
|
||||
await page.waitForTimeout(200)
|
||||
// hover over vertical line
|
||||
await u.canvasLocator.hover({ position: { x, y: 325 } })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
// click exit sketch
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
await hoverOverNothing()
|
||||
await page.waitForTimeout(200)
|
||||
// hover over horizontal line
|
||||
await page.mouse.move(858, y, { steps: 5 })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
// hover over vertical line
|
||||
await page.mouse.move(x, 325)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
// hover over vertical line
|
||||
await page.mouse.move(857, y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
// now click it
|
||||
await page.mouse.click(857, y)
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
await hoverOverNothing()
|
||||
x = 975
|
||||
y = 468
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.move(x, 419, { steps: 5 })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
await page.mouse.move(855, y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await hoverOverNothing()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await page.mouse.move(x, 419)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
await page.mouse.move(855, y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
})
|
||||
})
|
938
e2e/playwright/testing-constraints.spec.ts
Normal file
@ -0,0 +1,938 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown, TEST_COLORS } from './test-utils'
|
||||
import { XOR } from 'lib/utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Testing constraints', () => {
|
||||
test('Can constrain line length', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
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()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// enter sketch again
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(500) // wait for animation
|
||||
|
||||
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.getByRole('button', { name: 'Length', exact: true }).click()
|
||||
await page.getByText('Add constraining value').click()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const length001 = 20const sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
||||
)
|
||||
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
await page.waitForTimeout(500) // wait for animation
|
||||
|
||||
// Exit sketch
|
||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
test(`Test remove constraints`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const yo = 79
|
||||
const 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, 28.97 + 5], %)
|
||||
const 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], %, $seg01)').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.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
await page.getByRole('button', { name: 'remove constraints' }).click()
|
||||
|
||||
await page.getByText('line([39.13, 68.63], %)').click()
|
||||
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
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||
})
|
||||
test.describe('Test perpendicular distance constraint', () => {
|
||||
const cases = [
|
||||
{
|
||||
testName: 'Add variable',
|
||||
offset: '-offset001',
|
||||
},
|
||||
{
|
||||
testName: 'No variable',
|
||||
offset: '-128.05',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, offset } of cases) {
|
||||
test(`${testName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const yo = 5
|
||||
const 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([41.19, 28.97], %)
|
||||
const 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], %, $seg01)').click()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
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.down('Shift')
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||
await page.keyboard.up('Shift')
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
await page
|
||||
.getByRole('button', { name: 'Perpendicular Distance' })
|
||||
.click()
|
||||
|
||||
const createNewVariableCheckbox = page.getByTestId(
|
||||
'create-new-variable-checkbox'
|
||||
)
|
||||
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},`
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
})
|
||||
test.describe('Test distance between constraint', () => {
|
||||
const cases = [
|
||||
{
|
||||
testName: 'Add variable',
|
||||
constraint: 'horizontal distance',
|
||||
value: 'segEndX(seg01) + xDis001, 61.34',
|
||||
},
|
||||
{
|
||||
testName: 'No variable',
|
||||
constraint: 'horizontal distance',
|
||||
value: 'segEndX(seg01) + 88.08, 61.34',
|
||||
},
|
||||
{
|
||||
testName: 'Add variable',
|
||||
constraint: 'vertical distance',
|
||||
value: '154.9, segEndY(seg01) - yDis001',
|
||||
},
|
||||
{
|
||||
testName: 'No variable',
|
||||
constraint: 'vertical distance',
|
||||
value: '154.9, segEndY(seg01) - 42.32',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, value, constraint } of cases) {
|
||||
test(`${constraint} - ${testName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const yo = 5
|
||||
const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([41.19, 28.97], %)
|
||||
const 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 [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.down('Shift')
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||
await page.keyboard.up('Shift')
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
await page.getByRole('button', { name: constraint }).click()
|
||||
|
||||
const createNewVariableCheckbox = page.getByTestId(
|
||||
'create-new-variable-checkbox'
|
||||
)
|
||||
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()
|
||||
|
||||
// checking activeLines assures the cursors are where they should be
|
||||
const codeAfter = [
|
||||
`|> line([74.36, 130.4], %, $seg01)`,
|
||||
`|> lineTo([${value}], %)`,
|
||||
]
|
||||
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await Promise.all(
|
||||
activeLinesContent.map(async (line, i) => {
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
codeAfter[i]
|
||||
)
|
||||
// if the code is an active line then the cursor should be on that line
|
||||
await expect(line).toHaveText(codeAfter[i])
|
||||
})
|
||||
)
|
||||
|
||||
// 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('Test ABS distance constraint', () => {
|
||||
const cases = [
|
||||
{
|
||||
testName: 'Add variable',
|
||||
addVariable: true,
|
||||
constraint: 'Absolute X',
|
||||
value: 'xDis001, 61.34',
|
||||
},
|
||||
{
|
||||
testName: 'No variable',
|
||||
addVariable: false,
|
||||
constraint: 'Absolute X',
|
||||
value: '154.9, 61.34',
|
||||
},
|
||||
{
|
||||
testName: 'Add variable',
|
||||
addVariable: true,
|
||||
constraint: 'Absolute Y',
|
||||
value: '154.9, yDis001',
|
||||
},
|
||||
{
|
||||
testName: 'No variable',
|
||||
addVariable: false,
|
||||
constraint: 'Absolute Y',
|
||||
value: '154.9, 61.34',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, constraint } of cases) {
|
||||
test(`${constraint} - ${testName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const yo = 5
|
||||
const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([41.19, 28.97], %)
|
||||
const 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 Promise.all([
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||
])
|
||||
|
||||
if (constraint === 'Absolute X') {
|
||||
await page.mouse.click(600, 130)
|
||||
} else {
|
||||
await page.mouse.click(900, 250)
|
||||
}
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||
await page.keyboard.up('Shift')
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
await page
|
||||
.getByRole('button', { name: constraint, exact: true })
|
||||
.click()
|
||||
|
||||
const createNewVariableCheckbox = page.getByTestId(
|
||||
'create-new-variable-checkbox'
|
||||
)
|
||||
const isChecked = await createNewVariableCheckbox.isChecked()
|
||||
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()
|
||||
|
||||
// checking activeLines assures the cursors are where they should be
|
||||
const codeAfter = [`|> lineTo([${value}], %)`]
|
||||
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await Promise.all(
|
||||
activeLinesContent.map(async (line, i) => {
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
codeAfter[i]
|
||||
)
|
||||
// if the code is an active line then the cursor should be on that line
|
||||
await expect(line).toHaveText(codeAfter[i])
|
||||
})
|
||||
)
|
||||
|
||||
// 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('Test Angle constraint double segment selection', () => {
|
||||
const cases = [
|
||||
{
|
||||
testName: 'Add variable',
|
||||
addVariable: true,
|
||||
axisSelect: false,
|
||||
value: 'segAng(seg01) + angle001',
|
||||
},
|
||||
{
|
||||
testName: 'No variable',
|
||||
addVariable: false,
|
||||
axisSelect: false,
|
||||
value: 'segAng(seg01) + 22.69',
|
||||
},
|
||||
{
|
||||
testName: 'Add variable, selecting axis',
|
||||
addVariable: true,
|
||||
axisSelect: true,
|
||||
value: 'QUARTER_TURN - angle001',
|
||||
},
|
||||
{
|
||||
testName: 'No variable, selecting axis',
|
||||
addVariable: false,
|
||||
axisSelect: true,
|
||||
value: 'QUARTER_TURN - 7',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, axisSelect } of cases) {
|
||||
test(`${testName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const yo = 5
|
||||
const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([41.19, 28.97], %)
|
||||
const 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 [line1, line3] = await Promise.all([
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
|
||||
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
|
||||
])
|
||||
|
||||
if (axisSelect) {
|
||||
await page.mouse.click(600, 130)
|
||||
} else {
|
||||
await page.mouse.click(line1.x, line1.y)
|
||||
}
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||
await page.keyboard.up('Shift')
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
await page.getByTestId('dropdown-constraint-angle').click()
|
||||
|
||||
const createNewVariableCheckbox = page.getByTestId(
|
||||
'create-new-variable-checkbox'
|
||||
)
|
||||
const isChecked = await createNewVariableCheckbox.isChecked()
|
||||
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()
|
||||
|
||||
// checking activeLines assures the cursors are where they should be
|
||||
const codeAfter = [
|
||||
'|> line([74.36, 130.4], %, $seg01)',
|
||||
`|> angledLine([${value}, 78.33], %)`,
|
||||
]
|
||||
if (axisSelect) codeAfter.shift()
|
||||
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await Promise.all(
|
||||
activeLinesContent.map(async (line, i) => {
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
codeAfter[i]
|
||||
)
|
||||
// if the code is an active line then the cursor should be on that line
|
||||
await expect(line).toHaveText(codeAfter[i])
|
||||
})
|
||||
)
|
||||
|
||||
// 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('Test Angle/Length constraint single selection', () => {
|
||||
const cases = [
|
||||
{
|
||||
testName: 'Angle - Add variable',
|
||||
addVariable: true,
|
||||
constraint: 'angle',
|
||||
value: 'angle001, 78.33',
|
||||
},
|
||||
{
|
||||
testName: 'Angle - No variable',
|
||||
addVariable: false,
|
||||
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 }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const yo = 5
|
||||
const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([41.19, 28.97], %)
|
||||
const 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 page.getByTestId('create-new-variable-checkbox').click()
|
||||
}
|
||||
await page
|
||||
.getByRole('button', { name: 'Add constraining value' })
|
||||
.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 = [
|
||||
{
|
||||
constraintName: 'Vertical',
|
||||
codeAfter: [
|
||||
`|> yLine(130.4, %)`,
|
||||
`|> yLine(77.79, %)`,
|
||||
`|> yLine(28.97, %)`,
|
||||
],
|
||||
},
|
||||
{
|
||||
codeAfter: [
|
||||
`|> xLine(74.36, %)`,
|
||||
`|> xLine(9.16, %)`,
|
||||
`|> xLine(41.19, %)`,
|
||||
],
|
||||
constraintName: 'Horizontal',
|
||||
},
|
||||
] as const
|
||||
for (const { codeAfter, constraintName } of cases) {
|
||||
test(`${constraintName}`, async ({ page }) => {
|
||||
await page.addInitScript(async (customCode) => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const yo = 5
|
||||
const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([41.19, 28.97], %)
|
||||
const 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 line1 = await u.getSegmentBodyCoords(
|
||||
`[data-overlay-index="${0}"]`
|
||||
)
|
||||
const line3 = await u.getSegmentBodyCoords(
|
||||
`[data-overlay-index="${2}"]`
|
||||
)
|
||||
const line4 = await u.getSegmentBodyCoords(
|
||||
`[data-overlay-index="${3}"]`
|
||||
)
|
||||
|
||||
// select two segments by holding down shift
|
||||
await page.mouse.click(line1.x, line1.y)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page.mouse.click(line4.x, line4.y)
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
// check actives lines
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent).toHaveLength(codeAfter.length)
|
||||
|
||||
const constraintMenuButton = page.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
const constraintButton = page
|
||||
.getByRole('button', {
|
||||
name: constraintName,
|
||||
})
|
||||
.first()
|
||||
|
||||
// apply the constraint
|
||||
await constraintMenuButton.click()
|
||||
await constraintButton.click({ delay: 200 })
|
||||
|
||||
// check there are still 3 cursors (they should stay on the same lines as before constraint was applied)
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(codeAfter.length)
|
||||
|
||||
// check both cursors are where they should be after constraint is applied and the code is correct
|
||||
await Promise.all(
|
||||
activeLinesContent.map(async (line, i) => {
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
codeAfter[i]
|
||||
)
|
||||
// if the code is an active line then the cursor should be on that line
|
||||
await expect(line).toHaveText(codeAfter[i])
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
test.describe('Two segment - no modal constraints', () => {
|
||||
const cases = [
|
||||
{
|
||||
codeAfter: `|> angledLine([83, segLen(seg01)], %)`,
|
||||
constraintName: 'Equal Length',
|
||||
},
|
||||
{
|
||||
codeAfter: `|> angledLine([segAng(seg01), 78.33], %)`,
|
||||
constraintName: 'Parallel',
|
||||
},
|
||||
{
|
||||
codeAfter: `|> lineTo([segEndX(seg01), 61.34], %)`,
|
||||
constraintName: 'Vertically Align',
|
||||
},
|
||||
{
|
||||
codeAfter: `|> lineTo([154.9, segEndY(seg01)], %)`,
|
||||
constraintName: 'Horizontally Align',
|
||||
},
|
||||
] as const
|
||||
for (const { codeAfter, constraintName } of cases) {
|
||||
test(`${constraintName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const yo = 5
|
||||
const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
const 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 line1 = await u.getBoundingBox(`[data-overlay-index="${0}"]`)
|
||||
const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`)
|
||||
|
||||
// select two segments by holding down shift
|
||||
await page.mouse.click(line1.x - 20, line1.y + 20)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.click(line3.x - 3, line3.y + 20)
|
||||
await page.keyboard.up('Shift')
|
||||
const constraintMenuButton = page.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
const constraintButton = page.getByRole('button', {
|
||||
name: constraintName,
|
||||
})
|
||||
|
||||
// apply the constraint
|
||||
await constraintMenuButton.click()
|
||||
await constraintButton.click()
|
||||
|
||||
await expect(page.locator('.cm-content')).toContainText(codeAfter)
|
||||
// expect the string 'seg01' to appear twice in '.cm-content' the tag segment and referencing the tag
|
||||
const content = await page.locator('.cm-content').innerText()
|
||||
await expect(content.match(/seg01/g)).toHaveLength(2)
|
||||
// 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
|
||||
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(
|
||||
'|> line([74.36, 130.4], %, $seg01)'
|
||||
)
|
||||
await expect(activeLinesContent[1]).toHaveText(codeAfter)
|
||||
})
|
||||
}
|
||||
})
|
||||
test.describe('Axis & segment - no modal constraints', () => {
|
||||
const cases = [
|
||||
{
|
||||
codeAfter: `|> lineTo([154.9, ZERO], %)`,
|
||||
axisClick: { x: 950, y: 250 },
|
||||
constraintName: 'Snap To X',
|
||||
},
|
||||
{
|
||||
codeAfter: `|> lineTo([ZERO, 61.34], %)`,
|
||||
axisClick: { x: 600, y: 150 },
|
||||
constraintName: 'Snap To Y',
|
||||
},
|
||||
] as const
|
||||
for (const { codeAfter, constraintName, axisClick } of cases) {
|
||||
test(`${constraintName}`, async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const yo = 5
|
||||
const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
const 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.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.keyboard.down('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(axisClick.x, axisClick.y)
|
||||
await page.keyboard.up('Shift')
|
||||
const constraintMenuButton = page.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
const constraintButton = page.getByRole('button', {
|
||||
name: constraintName,
|
||||
})
|
||||
|
||||
// apply the constraint
|
||||
await constraintMenuButton.click()
|
||||
await expect(constraintButton).toBeVisible()
|
||||
await constraintButton.click()
|
||||
|
||||
// check the cursor is where is should be after constraint is applied
|
||||
await expect(page.locator('.cm-content')).toContainText(codeAfter)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(codeAfter)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test('Horizontally constrained line remains selected after applying constraint', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.setTimeout(70_000)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-1.05, -1.07], %)
|
||||
|> line([3.79, 2.68], %, $seg01)
|
||||
|> line([3.13, -2.4], %)`
|
||||
)
|
||||
})
|
||||
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(
|
||||
{ timeout: 10_000 }
|
||||
)
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
const lineBefore = await u.getSegmentBodyCoords(
|
||||
`[data-overlay-index="1"]`,
|
||||
0
|
||||
)
|
||||
expect(
|
||||
await u.getGreatestPixDiff(lineBefore, TEST_COLORS.WHITE)
|
||||
).toBeLessThan(3)
|
||||
await page.mouse.move(lineBefore.x, lineBefore.y)
|
||||
await page.waitForTimeout(50)
|
||||
await page.mouse.click(lineBefore.x, lineBefore.y)
|
||||
expect(
|
||||
await u.getGreatestPixDiff(lineBefore, TEST_COLORS.BLUE)
|
||||
).toBeLessThan(3)
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
await page.getByRole('button', { name: 'Horizontal', exact: true }).click()
|
||||
|
||||
let activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent[0]).toHaveText(`|> xLine(3.13, %)`)
|
||||
|
||||
// If the overlay-angle is updated the THREE.js scene is in a good state
|
||||
await expect(
|
||||
await page.locator('[data-overlay-index="1"]')
|
||||
).toHaveAttribute('data-overlay-angle', '0')
|
||||
|
||||
const lineAfter = await u.getSegmentBodyCoords(
|
||||
`[data-overlay-index="1"]`,
|
||||
0
|
||||
)
|
||||
expect(
|
||||
await u.getGreatestPixDiff(lineAfter, TEST_COLORS.BLUE)
|
||||
).toBeLessThan(3)
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
// await expect(page.getByRole('button', { name: 'length', exact: true })).toBeVisible()
|
||||
await page.waitForTimeout(200)
|
||||
// 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()
|
||||
|
||||
activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
||||
|
||||
// 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(2)
|
||||
})
|
||||
})
|
249
e2e/playwright/testing-gizmo.spec.ts
Normal file
@ -0,0 +1,249 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { TEST_CODE_GIZMO } from './storageStates'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Testing Gizmo', () => {
|
||||
const cases = [
|
||||
{
|
||||
testDescription: 'top view',
|
||||
clickPosition: { x: 951, y: 385 },
|
||||
expectedCameraPosition: { x: 800, y: -152, z: 4886.02 },
|
||||
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||
},
|
||||
{
|
||||
testDescription: 'bottom view',
|
||||
clickPosition: { x: 951, y: 429 },
|
||||
expectedCameraPosition: { x: 800, y: -152, z: -4834.02 },
|
||||
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||
},
|
||||
{
|
||||
testDescription: 'right view',
|
||||
clickPosition: { x: 929, y: 417 },
|
||||
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
|
||||
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||
},
|
||||
{
|
||||
testDescription: 'left view',
|
||||
clickPosition: { x: 974, y: 397 },
|
||||
expectedCameraPosition: { x: -4060.02, y: -152, z: 26 },
|
||||
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||
},
|
||||
{
|
||||
testDescription: 'back view',
|
||||
clickPosition: { x: 967, y: 421 },
|
||||
expectedCameraPosition: { x: 800, y: 4708.02, z: 26 },
|
||||
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||
},
|
||||
{
|
||||
testDescription: 'front view',
|
||||
clickPosition: { x: 935, y: 393 },
|
||||
expectedCameraPosition: { x: 800, y: -5012.02, z: 26 },
|
||||
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||
},
|
||||
] as const
|
||||
for (const {
|
||||
clickPosition,
|
||||
expectedCameraPosition,
|
||||
expectedCameraTarget,
|
||||
testDescription,
|
||||
} of cases) {
|
||||
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 u.waitForAuthSkipAppStart()
|
||||
await page.waitForTimeout(100)
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: {
|
||||
x: 3000,
|
||||
y: 3000,
|
||||
z: 3000,
|
||||
},
|
||||
center: {
|
||||
x: 800,
|
||||
y: -152,
|
||||
z: 26,
|
||||
},
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.clearCommandLogs()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await u.waitForCmdReceive('default_camera_get_settings')
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await page.mouse.move(clickPosition.x, clickPosition.y)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(clickPosition.x, clickPosition.y)
|
||||
await page.mouse.move(0, 0)
|
||||
await u.waitForCmdReceive('default_camera_look_at')
|
||||
await u.clearCommandLogs()
|
||||
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await u.waitForCmdReceive('default_camera_get_settings')
|
||||
|
||||
await Promise.all([
|
||||
// position
|
||||
expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||
expectedCameraPosition.x.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||
expectedCameraPosition.y.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||
expectedCameraPosition.z.toString()
|
||||
),
|
||||
// target
|
||||
expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||
expectedCameraTarget.x.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||
expectedCameraTarget.y.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||
expectedCameraTarget.z.toString()
|
||||
),
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
test('Context menu and popover menu', async ({ page }) => {
|
||||
const testCase = {
|
||||
testDescription: 'Right view',
|
||||
expectedCameraPosition: { x: 5660.02, y: -152, z: 26 },
|
||||
expectedCameraTarget: { x: 800, y: -152, z: 26 },
|
||||
}
|
||||
|
||||
// Test prelude taken from the above test
|
||||
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 u.waitForAuthSkipAppStart()
|
||||
await page.waitForTimeout(100)
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: {
|
||||
x: 3000,
|
||||
y: 3000,
|
||||
z: 3000,
|
||||
},
|
||||
center: {
|
||||
x: 800,
|
||||
y: -152,
|
||||
z: 26,
|
||||
},
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.clearCommandLogs()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await u.waitForCmdReceive('default_camera_get_settings')
|
||||
|
||||
// Now find and select the correct
|
||||
// view from the context menu
|
||||
await u.clearCommandLogs()
|
||||
const gizmo = page.locator('[aria-label*=gizmo]')
|
||||
await gizmo.click({ button: 'right' })
|
||||
const buttonToTest = page.getByRole('button', {
|
||||
name: testCase.testDescription,
|
||||
})
|
||||
await expect(buttonToTest).toBeVisible()
|
||||
await buttonToTest.click()
|
||||
|
||||
// Now assert we've moved to the correct view
|
||||
// Taken from the above test
|
||||
await u.waitForCmdReceive('default_camera_look_at')
|
||||
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await u.waitForCmdReceive('default_camera_get_settings')
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
await Promise.all([
|
||||
// position
|
||||
expect(page.getByTestId('cam-x-position')).toHaveValue(
|
||||
testCase.expectedCameraPosition.x.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-y-position')).toHaveValue(
|
||||
testCase.expectedCameraPosition.y.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-z-position')).toHaveValue(
|
||||
testCase.expectedCameraPosition.z.toString()
|
||||
),
|
||||
// target
|
||||
expect(page.getByTestId('cam-x-target')).toHaveValue(
|
||||
testCase.expectedCameraTarget.x.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-y-target')).toHaveValue(
|
||||
testCase.expectedCameraTarget.y.toString()
|
||||
),
|
||||
expect(page.getByTestId('cam-z-target')).toHaveValue(
|
||||
testCase.expectedCameraTarget.z.toString()
|
||||
),
|
||||
])
|
||||
|
||||
// Now test the popover menu.
|
||||
// It has the same click handlers, so we can just
|
||||
// test that it opens and contains the same content.
|
||||
const gizmoPopoverButton = page.getByRole('button', {
|
||||
name: 'view settings',
|
||||
})
|
||||
await expect(gizmoPopoverButton).toBeVisible()
|
||||
await gizmoPopoverButton.click()
|
||||
await expect(buttonToTest).toBeVisible()
|
||||
})
|
||||
})
|