Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
a3ff0a45eb | |||
4617ad0fed | |||
5fa51a2f92 | |||
4218777afb | |||
8b1b462e29 | |||
2bc99ba39b | |||
ffe0da6dcd | |||
d27afb8c74 | |||
1c778bf373 | |||
5df8a943a9 | |||
ab729dbcdb | |||
84865eaed0 | |||
543e809739 | |||
61b669cf4e | |||
75f1aaa824 | |||
f4848d7dea | |||
a0167f6ba6 |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -89,16 +89,20 @@ jobs:
|
|||||||
- run: yarn build:wasm
|
- run: yarn build:wasm
|
||||||
|
|
||||||
- run: yarn simpleserver:ci
|
- run: yarn simpleserver:ci
|
||||||
|
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||||
|
|
||||||
- name: Install Chromium Browser
|
- name: Install Chromium Browser
|
||||||
|
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||||
run: yarn playwright install chromium --with-deps
|
run: yarn playwright install chromium --with-deps
|
||||||
|
|
||||||
- name: run unit tests
|
- name: run unit tests
|
||||||
|
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||||
run: yarn test:nowatch
|
run: yarn test:nowatch
|
||||||
env:
|
env:
|
||||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
|
|
||||||
- name: check for changes
|
- name: check for changes
|
||||||
|
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||||
id: git-check
|
id: git-check
|
||||||
run: |
|
run: |
|
||||||
git add src/lang/std/artifactMapGraphs
|
git add src/lang/std/artifactMapGraphs
|
||||||
@ -107,7 +111,7 @@ jobs:
|
|||||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
- name: Commit changes, if any
|
- name: Commit changes, if any
|
||||||
if: steps.git-check.outputs.modified == 'true'
|
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' && steps.git-check.outputs.modified == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
git config --local user.name "github-actions[bot]"
|
git config --local user.name "github-actions[bot]"
|
||||||
@ -531,7 +535,7 @@ jobs:
|
|||||||
project_id: kittycadapi
|
project_id: kittycadapi
|
||||||
|
|
||||||
- name: Upload release files to public bucket
|
- 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:
|
with:
|
||||||
path: artifact
|
path: artifact
|
||||||
glob: '*/Zoo*'
|
glob: '*/Zoo*'
|
||||||
@ -539,13 +543,13 @@ jobs:
|
|||||||
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
||||||
|
|
||||||
- name: Upload update endpoint to public bucket
|
- 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:
|
with:
|
||||||
path: last_update.json
|
path: last_update.json
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
|
|
||||||
- name: Upload download endpoint to public bucket
|
- 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:
|
with:
|
||||||
path: last_download.json
|
path: last_download.json
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
|
43
.github/workflows/playwright.yml
vendored
43
.github/workflows/playwright.yml
vendored
@ -34,8 +34,13 @@ jobs:
|
|||||||
- 'src/wasm-lib/**'
|
- 'src/wasm-lib/**'
|
||||||
|
|
||||||
playwright-ubuntu:
|
playwright-ubuntu:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 30
|
||||||
runs-on: ubuntu-latest-8-cores
|
runs-on: ubuntu-latest-8-cores
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
shardIndex: [1, 2, 3, 4]
|
||||||
|
shardTotal: [4]
|
||||||
needs: check-rust-changes
|
needs: check-rust-changes
|
||||||
steps:
|
steps:
|
||||||
- name: Tune GitHub-hosted runner network
|
- name: Tune GitHub-hosted runner network
|
||||||
@ -107,7 +112,7 @@ jobs:
|
|||||||
run: yarn build:local
|
run: yarn build:local
|
||||||
- name: Run ubuntu/chrome snapshots
|
- name: Run ubuntu/chrome snapshots
|
||||||
run: |
|
run: |
|
||||||
yarn playwright test --project="Google Chrome" --retries="3" --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:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
@ -115,7 +120,7 @@ jobs:
|
|||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report-ubuntu-snapshot-${{ github.sha }}
|
name: playwright-report-ubuntu-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@ -149,7 +154,7 @@ jobs:
|
|||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: steps.git-check.outputs.modified == 'true'
|
if: steps.git-check.outputs.modified == 'true'
|
||||||
with:
|
with:
|
||||||
name: playwright-report-ubuntu-${{ github.sha }}
|
name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
# if have previous run results, use them
|
# if have previous run results, use them
|
||||||
@ -157,7 +162,7 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
name: test-results-ubuntu-${{ github.sha }}
|
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
- name: Run ubuntu/chrome flow (with retries)
|
- name: Run ubuntu/chrome flow (with retries)
|
||||||
id: retry
|
id: retry
|
||||||
@ -166,7 +171,7 @@ jobs:
|
|||||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||||
# if no last run artifact, than run plawright normally
|
# if no last run artifact, than run plawright normally
|
||||||
echo "run playwright normally"
|
echo "run playwright normally"
|
||||||
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
|
# # send to axiom
|
||||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
@ -181,7 +186,7 @@ jobs:
|
|||||||
if [[ $failed_tests -gt 0 ]]; then
|
if [[ $failed_tests -gt 0 ]]; then
|
||||||
echo "retried=true" >>$GITHUB_OUTPUT
|
echo "retried=true" >>$GITHUB_OUTPUT
|
||||||
echo "run playwright with last failed tests and retry $retry"
|
echo "run playwright with last failed tests and retry $retry"
|
||||||
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
|
# send to axiom
|
||||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||||
retry=$((retry + 1))
|
retry=$((retry + 1))
|
||||||
@ -216,21 +221,26 @@ jobs:
|
|||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: test-results-ubuntu-${{ github.sha }}
|
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report-ubuntu-${{ github.sha }}
|
name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
playwright-macos:
|
playwright-macos:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 30
|
||||||
runs-on: macos-14-large
|
runs-on: macos-14
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
shardIndex: [1, 2, 3, 4]
|
||||||
|
shardTotal: [4]
|
||||||
needs: check-rust-changes
|
needs: check-rust-changes
|
||||||
steps:
|
steps:
|
||||||
- name: Tune GitHub-hosted runner network
|
- name: Tune GitHub-hosted runner network
|
||||||
@ -306,7 +316,7 @@ jobs:
|
|||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
name: test-results-macos-${{ github.sha }}
|
name: test-results-macos-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
- name: Run macos/safari flow (with retries)
|
- name: Run macos/safari flow (with retries)
|
||||||
id: retry
|
id: retry
|
||||||
@ -315,7 +325,7 @@ jobs:
|
|||||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||||
# if no last run artifact, than run plawright normally
|
# if no last run artifact, than run plawright normally
|
||||||
echo "run playwright normally"
|
echo "run playwright normally"
|
||||||
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
|
# # send to axiom
|
||||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
@ -330,7 +340,7 @@ jobs:
|
|||||||
if [[ $failed_tests -gt 0 ]]; then
|
if [[ $failed_tests -gt 0 ]]; then
|
||||||
echo "retried=true" >>$GITHUB_OUTPUT
|
echo "retried=true" >>$GITHUB_OUTPUT
|
||||||
echo "run playwright with last failed tests and retry $retry"
|
echo "run playwright with last failed tests and retry $retry"
|
||||||
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
|
# send to axiom
|
||||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||||
retry=$((retry + 1))
|
retry=$((retry + 1))
|
||||||
@ -360,15 +370,14 @@ jobs:
|
|||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: test-results-macos-${{ github.sha }}
|
name: test-results-macos-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: playwright-report-macos-${{ github.sha }}
|
name: playwright-report-macos-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
|
37
docs/kcl/assertEqual.md
Normal file
37
docs/kcl/assertEqual.md
Normal file
File diff suppressed because one or more lines are too long
@ -21,6 +21,7 @@ layout: manual
|
|||||||
* [`arc`](kcl/arc)
|
* [`arc`](kcl/arc)
|
||||||
* [`asin`](kcl/asin)
|
* [`asin`](kcl/asin)
|
||||||
* [`assert`](kcl/assert)
|
* [`assert`](kcl/assert)
|
||||||
|
* [`assertEqual`](kcl/assertEqual)
|
||||||
* [`assertGreaterThan`](kcl/assertGreaterThan)
|
* [`assertGreaterThan`](kcl/assertGreaterThan)
|
||||||
* [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq)
|
* [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq)
|
||||||
* [`assertLessThan`](kcl/assertLessThan)
|
* [`assertLessThan`](kcl/assertLessThan)
|
||||||
|
@ -54791,6 +54791,62 @@
|
|||||||
"const myVar = true\nassert(myVar, \"should always be true\")"
|
"const myVar = true\nassert(myVar, \"should always be true\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "assertEqual",
|
||||||
|
"summary": "Check that a numerical value equals another at runtime,",
|
||||||
|
"description": "otherwise raise an error.",
|
||||||
|
"tags": [],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "left",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "right",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "epsilon",
|
||||||
|
"type": "number",
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "double"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "message",
|
||||||
|
"type": "string",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnValue": {
|
||||||
|
"name": "",
|
||||||
|
"type": "()",
|
||||||
|
"schema": {
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"unpublished": false,
|
||||||
|
"deprecated": false,
|
||||||
|
"examples": [
|
||||||
|
"let n = 1.0285\nlet m = 1.0286\nassertEqual(n, m, 0.01, \"n is within the given tolerance for m\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "assertGreaterThan",
|
"name": "assertGreaterThan",
|
||||||
"summary": "Check that a numerical value is greater than another at runtime,",
|
"summary": "Check that a numerical value is greater than another at runtime,",
|
||||||
@ -223435,7 +223491,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "xLine",
|
"name": "xLine",
|
||||||
"summary": "Draw a line on the x-axis.",
|
"summary": "Draw a line parallel to the X-axis, with the given length.",
|
||||||
"description": "",
|
"description": "",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"args": [
|
"args": [
|
||||||
@ -230069,8 +230125,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "xLineTo",
|
"name": "xLineTo",
|
||||||
"summary": "Draw a line to a point on the x-axis.",
|
"summary": "Draw a line parallel to the X axis, that ends at the given X.",
|
||||||
"description": "",
|
"description": "E.g. if the previous line ended at (1, 1), then xLineTo(4) draws a line from (1, 1) to (4, 1)",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
@ -236703,7 +236759,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "yLine",
|
"name": "yLine",
|
||||||
"summary": "Draw a line on the y-axis.",
|
"summary": "Draw a line parallel to the Y-axis, with the given length.",
|
||||||
"description": "",
|
"description": "",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"args": [
|
"args": [
|
||||||
@ -243337,8 +243393,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "yLineTo",
|
"name": "yLineTo",
|
||||||
"summary": "Draw a line to a point on the y-axis.",
|
"summary": "Draw a line parallel to the Y axis, that ends at the given Y.",
|
||||||
"description": "",
|
"description": "E.g. if the previous line ended at (1, 1), then yLineTo(4) draws a line from (1, 1) to (1, 4)",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "xLine"
|
title: "xLine"
|
||||||
excerpt: "Draw a line on the x-axis."
|
excerpt: "Draw a line parallel to the X-axis, with the given length."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Draw a line on the x-axis.
|
Draw a line parallel to the X-axis, with the given length.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: "xLineTo"
|
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
|
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
|
```js
|
||||||
xLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
xLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "yLine"
|
title: "yLine"
|
||||||
excerpt: "Draw a line on the y-axis."
|
excerpt: "Draw a line parallel to the Y-axis, with the given length."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Draw a line on the y-axis.
|
Draw a line parallel to the Y-axis, with the given length.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: "yLineTo"
|
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
|
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
|
```js
|
||||||
yLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
yLineTo(to: number, sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
||||||
|
@ -2535,18 +2535,29 @@ test.describe('Onboarding tests', () => {
|
|||||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
// Test that the text in this step is correct
|
||||||
const avatarLocator = await page
|
const sidebar = page.getByTestId('user-sidebar-toggle')
|
||||||
.getByTestId('user-sidebar-toggle')
|
const avatar = sidebar.locator('img')
|
||||||
.locator('img')
|
const onboardingOverlayLocator = page
|
||||||
const onboardingOverlayLocator = await page
|
|
||||||
.getByTestId('onboarding-content')
|
.getByTestId('onboarding-content')
|
||||||
.locator('div')
|
.locator('div')
|
||||||
.nth(1)
|
.nth(1)
|
||||||
|
|
||||||
// Expect the avatar to be visible and for the text to reference it
|
// Expect the avatar to be visible and for the text to reference it
|
||||||
await expect(avatarLocator).not.toBeVisible()
|
await expect(avatar).not.toBeVisible()
|
||||||
await expect(onboardingOverlayLocator).toBeVisible()
|
await expect(onboardingOverlayLocator).toBeVisible()
|
||||||
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -3894,6 +3905,39 @@ const extrude001 = extrude(distance001, sketch001)`.replace(
|
|||||||
|
|
||||||
test.describe('Regression tests', () => {
|
test.describe('Regression tests', () => {
|
||||||
// bugs we found that don't fit neatly into other categories
|
// bugs we found that don't fit neatly into other categories
|
||||||
|
test('bad model has inline error #3251', async ({ page }) => {
|
||||||
|
// 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 }) => {
|
test('executes on load', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -8333,6 +8377,83 @@ test.describe('Code pane and errors', () => {
|
|||||||
await badge.click()
|
await badge.click()
|
||||||
|
|
||||||
// Ensure we have an error diagnostic.
|
// Ensure we have an error diagnostic.
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
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()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -42,7 +42,10 @@ test.beforeEach(async ({ page }) => {
|
|||||||
|
|
||||||
test.setTimeout(60_000)
|
test.setTimeout(60_000)
|
||||||
|
|
||||||
test('exports of each format should work', async ({ page, context }) => {
|
test(
|
||||||
|
'exports of each format should work',
|
||||||
|
{ tag: '@snapshot' },
|
||||||
|
async ({ page, context }) => {
|
||||||
// FYI this test doesn't work with only engine running locally
|
// FYI this test doesn't work with only engine running locally
|
||||||
// And you will need to have the KittyCAD CLI installed
|
// And you will need to have the KittyCAD CLI installed
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -296,7 +299,8 @@ const part001 = startSketchOn('-XZ')
|
|||||||
console.log(`snapshot failed for ${modelPath}`)
|
console.log(`snapshot failed for ${modelPath}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|
const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
@ -357,7 +361,10 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|
|||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe('extrude on default planes should be stable', () => {
|
test.describe(
|
||||||
|
'extrude on default planes should be stable',
|
||||||
|
{ tag: '@snapshot' },
|
||||||
|
() => {
|
||||||
test('XY', async ({ page, context }) => {
|
test('XY', async ({ page, context }) => {
|
||||||
await extrudeDefaultPlane(context, page, 'XY')
|
await extrudeDefaultPlane(context, page, 'XY')
|
||||||
})
|
})
|
||||||
@ -381,9 +388,13 @@ test.describe('extrude on default planes should be stable', () => {
|
|||||||
test('-YZ', async ({ page, context }) => {
|
test('-YZ', async ({ page, context }) => {
|
||||||
await extrudeDefaultPlane(context, page, '-YZ')
|
await extrudeDefaultPlane(context, page, '-YZ')
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('Draft segments should look right', async ({ page, context }) => {
|
test(
|
||||||
|
'Draft segments should look right',
|
||||||
|
{ tag: '@snapshot' },
|
||||||
|
async ({ page, context }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
@ -394,7 +405,9 @@ test('Draft segments should look right', async ({ page, context }) => {
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
@ -442,9 +455,13 @@ test('Draft segments should look right', async ({ page, context }) => {
|
|||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('Draft rectangles should look right', async ({ page, context }) => {
|
test(
|
||||||
|
'Draft rectangles should look right',
|
||||||
|
{ tag: '@snapshot' },
|
||||||
|
async ({ page, context }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
@ -455,7 +472,9 @@ test('Draft rectangles should look right', async ({ page, context }) => {
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
@ -490,9 +509,13 @@ test('Draft rectangles should look right', async ({ page, context }) => {
|
|||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test.describe('Client side scene scale should match engine scale', () => {
|
test.describe(
|
||||||
|
'Client side scene scale should match engine scale',
|
||||||
|
{ tag: '@snapshot' },
|
||||||
|
() => {
|
||||||
test('Inch scale', async ({ page }) => {
|
test('Inch scale', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
@ -680,9 +703,13 @@ test.describe('Client side scene scale should match engine scale', () => {
|
|||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('Sketch on face with none z-up', async ({ page, context }) => {
|
test(
|
||||||
|
'Sketch on face with none z-up',
|
||||||
|
{ tag: '@snapshot' },
|
||||||
|
async ({ page, context }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
await context.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -728,7 +755,9 @@ const part002 = startSketchOn(part001, seg01)
|
|||||||
|
|
||||||
// click at 641, 135
|
// click at 641, 135
|
||||||
await page.mouse.click(641, 135)
|
await page.mouse.click(641, 135)
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(
|
||||||
|
previousCodeContent
|
||||||
|
)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
@ -736,9 +765,13 @@ const part002 = startSketchOn(part001, seg01)
|
|||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('Zoom to fit on load - solid 2d', async ({ page, context }) => {
|
test(
|
||||||
|
'Zoom to fit on load - solid 2d',
|
||||||
|
{ tag: '@snapshot' },
|
||||||
|
async ({ page, context }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -772,9 +805,13 @@ test('Zoom to fit on load - solid 2d', async ({ page, context }) => {
|
|||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('Zoom to fit on load - solid 3d', async ({ page, context }) => {
|
test(
|
||||||
|
'Zoom to fit on load - solid 3d',
|
||||||
|
{ tag: '@snapshot' },
|
||||||
|
async ({ page, context }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -809,9 +846,10 @@ test('Zoom to fit on load - solid 3d', async ({ page, context }) => {
|
|||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test.describe('Grid visibility', () => {
|
test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||||
test('Grid turned off', async ({ page }) => {
|
test('Grid turned off', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const stream = page.getByTestId('stream')
|
const stream = page.getByTestId('stream')
|
||||||
|
@ -337,7 +337,24 @@ fn svg = (surface, origin, depth) => {
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(depth, %)
|
|> extrude(depth, %)
|
||||||
|
|
||||||
"thing";kajsnd;akjsnd
|
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
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,23 @@ export const TEST_COLORS = {
|
|||||||
BLUE: [0, 0, 255] as TestColor,
|
BLUE: [0, 0, 255] as TestColor,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
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) {
|
async function waitForPageLoad(page: Page) {
|
||||||
// wait for all spinners to be gone
|
// wait for all spinners to be gone
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||||
@ -218,9 +235,12 @@ async function waitForAuthAndLsp(page: Page) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
if (process.env.CI) {
|
||||||
|
await waitForPageLoadWithRetry(page)
|
||||||
|
} else {
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await waitForPageLoad(page)
|
await waitForPageLoad(page)
|
||||||
|
}
|
||||||
|
|
||||||
return waitForLspPromise
|
return waitForLspPromise
|
||||||
}
|
}
|
||||||
@ -234,6 +254,7 @@ export async function getUtils(page: Page) {
|
|||||||
return {
|
return {
|
||||||
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||||
waitForPageLoad: () => waitForPageLoad(page),
|
waitForPageLoad: () => waitForPageLoad(page),
|
||||||
|
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
|
||||||
removeCurrentCode: () => removeCurrentCode(page),
|
removeCurrentCode: () => removeCurrentCode(page),
|
||||||
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
|
||||||
updateCamPosition: async (xyz: [number, number, number]) => {
|
updateCamPosition: async (xyz: [number, number, number]) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.24.8",
|
"version": "0.24.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.17.0",
|
"@codemirror/autocomplete": "^6.17.0",
|
||||||
|
@ -18,7 +18,7 @@ export default defineConfig({
|
|||||||
/* Do not retry */
|
/* Do not retry */
|
||||||
retries: process.env.CI ? 0 : 0,
|
retries: process.env.CI ? 0 : 0,
|
||||||
/* Different amount of parallelism on CI and local. */
|
/* Different amount of parallelism on CI and local. */
|
||||||
workers: process.env.CI ? 4 : 4,
|
workers: process.env.CI ? 1 : 4,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: [
|
reporter: [
|
||||||
[process.env.CI ? 'dot' : 'list'],
|
[process.env.CI ? 'dot' : 'list'],
|
||||||
|
@ -80,5 +80,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.24.8"
|
"version": "0.24.9"
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
historyKeymap,
|
historyKeymap,
|
||||||
history,
|
history,
|
||||||
} from '@codemirror/commands'
|
} from '@codemirror/commands'
|
||||||
import { lintGutter, lintKeymap } from '@codemirror/lint'
|
import { diagnosticCount, lintGutter, lintKeymap } from '@codemirror/lint'
|
||||||
import {
|
import {
|
||||||
foldGutter,
|
foldGutter,
|
||||||
foldKeymap,
|
foldKeymap,
|
||||||
@ -196,7 +196,10 @@ export const KclEditorPane = () => {
|
|||||||
|
|
||||||
// On first load of this component, ensure we show the current errors
|
// On first load of this component, ensure we show the current errors
|
||||||
// in the editor.
|
// in the editor.
|
||||||
|
// Make sure we don't add them twice.
|
||||||
|
if (diagnosticCount(_editorView.state) === 0) {
|
||||||
kclManager.setDiagnosticsForCurrentErrors()
|
kclManager.setDiagnosticsForCurrentErrors()
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,7 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
},
|
},
|
||||||
onClick: (e) => {
|
onClick: (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
editorManager.scrollToFirstDiagnosticIfExists()
|
editorManager.scrollToFirstErrorDiagnosticIfExists()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -141,14 +141,14 @@ export default class EditorManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToFirstDiagnosticIfExists() {
|
scrollToFirstErrorDiagnosticIfExists() {
|
||||||
if (!this._editorView) return
|
if (!this._editorView) return
|
||||||
|
|
||||||
let firstDiagnosticPos: [number, number] | null = null
|
let firstDiagnosticPos: [number, number] | null = null
|
||||||
forEachDiagnostic(
|
forEachDiagnostic(
|
||||||
this._editorView.state,
|
this._editorView.state,
|
||||||
(d: Diagnostic, from: number, to: number) => {
|
(d: Diagnostic, from: number, to: number) => {
|
||||||
if (!firstDiagnosticPos) {
|
if (!firstDiagnosticPos && d.severity === 'error') {
|
||||||
firstDiagnosticPos = [from, to]
|
firstDiagnosticPos = [from, to]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,7 +161,11 @@ export default class EditorManager {
|
|||||||
selection: EditorSelection.create([
|
selection: EditorSelection.create([
|
||||||
EditorSelection.cursor(firstDiagnosticPos[0]),
|
EditorSelection.cursor(firstDiagnosticPos[0]),
|
||||||
]),
|
]),
|
||||||
effects: [EditorView.scrollIntoView(firstDiagnosticPos[0])],
|
effects: [
|
||||||
|
EditorView.scrollIntoView(
|
||||||
|
EditorSelection.range(firstDiagnosticPos[0], firstDiagnosticPos[1])
|
||||||
|
),
|
||||||
|
],
|
||||||
annotations: [
|
annotations: [
|
||||||
updateOutsideEditorEvent,
|
updateOutsideEditorEvent,
|
||||||
Transaction.addToHistory.of(false),
|
Transaction.addToHistory.of(false),
|
||||||
|
@ -791,6 +791,7 @@ export function isSingleCursorInPipe(
|
|||||||
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
||||||
const nodeTypes = pathToNode.map(([, type]) => type)
|
const nodeTypes = pathToNode.map(([, type]) => type)
|
||||||
if (nodeTypes.includes('FunctionExpression')) return false
|
if (nodeTypes.includes('FunctionExpression')) return false
|
||||||
|
if (!nodeTypes.includes('VariableDeclaration')) return false
|
||||||
if (nodeTypes.includes('PipeExpression')) return true
|
if (nodeTypes.includes('PipeExpression')) return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -458,6 +458,9 @@ async function GraphTheGraph(
|
|||||||
await page.waitForSelector('#plotly-graph')
|
await page.waitForSelector('#plotly-graph')
|
||||||
const element = await page.$('#plotly-graph')
|
const element = await page.$('#plotly-graph')
|
||||||
|
|
||||||
|
// wait an extra bit for things to settle
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await element.screenshot({
|
await element.screenshot({
|
||||||
path: `./e2e/playwright/temp3.png`,
|
path: `./e2e/playwright/temp3.png`,
|
||||||
|
@ -240,6 +240,7 @@ export function getArtifactsToUpdate({
|
|||||||
const response = responseMap[id]
|
const response = responseMap[id]
|
||||||
const cmd = command.cmd
|
const cmd = command.cmd
|
||||||
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
|
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
|
||||||
|
if (!response) return returnArr
|
||||||
if (cmd.type === 'enable_sketch_mode') {
|
if (cmd.type === 'enable_sketch_mode') {
|
||||||
const plane = getArtifact(currentPlaneId)
|
const plane = getArtifact(currentPlaneId)
|
||||||
const pathIds = plane?.type === 'plane' ? plane?.pathIds : []
|
const pathIds = plane?.type === 'plane' ? plane?.pathIds : []
|
||||||
@ -316,7 +317,7 @@ export function getArtifactsToUpdate({
|
|||||||
artifact: { ...path, segIds: [id] },
|
artifact: { ...path, segIds: [id] },
|
||||||
})
|
})
|
||||||
if (
|
if (
|
||||||
response.type === 'modeling' &&
|
response?.type === 'modeling' &&
|
||||||
response.data.modeling_response.type === 'close_path'
|
response.data.modeling_response.type === 'close_path'
|
||||||
) {
|
) {
|
||||||
returnArr.push({
|
returnArr.push({
|
||||||
|
@ -2,7 +2,7 @@ import { Program, SourceRange } from 'lang/wasm'
|
|||||||
import { VITE_KC_API_WS_MODELING_URL } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
import { deferExecution, uuidv4 } from 'lib/utils'
|
import { deferExecution, isOverlap, uuidv4 } from 'lib/utils'
|
||||||
import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme'
|
import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme'
|
||||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
import {
|
import {
|
||||||
@ -1899,15 +1899,10 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
range: SourceRange,
|
range: SourceRange,
|
||||||
commandTypeToTarget: string
|
commandTypeToTarget: string
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const values = Object.entries(this.artifactGraph)
|
for (const [artifactId, artifact] of this.artifactGraph) {
|
||||||
for (const [id, data] of values) {
|
if ('codeRef' in artifact && isOverlap(range, artifact.codeRef.range)) {
|
||||||
// // Our range selection seems to just select the cursor position, so either
|
if (commandTypeToTarget === artifact.type) return artifactId
|
||||||
// // of these can be right...
|
}
|
||||||
if (
|
|
||||||
(data.range[0] === range[0] || data.range[1] === range[1]) &&
|
|
||||||
data.type === commandTypeToTarget
|
|
||||||
)
|
|
||||||
return id
|
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import EditorManager from 'editor/manager'
|
|||||||
import { KclManager } from 'lang/KclSingleton'
|
import { KclManager } from 'lang/KclSingleton'
|
||||||
import CodeManager from 'lang/codeManager'
|
import CodeManager from 'lang/codeManager'
|
||||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
|
import { uuidv4 } from './utils'
|
||||||
|
|
||||||
export const codeManager = new CodeManager()
|
export const codeManager = new CodeManager()
|
||||||
|
|
||||||
@ -40,4 +41,14 @@ if (typeof window !== 'undefined') {
|
|||||||
;(window as any).enableFillet = () => {
|
;(window as any).enableFillet = () => {
|
||||||
;(window as any)._enableFillet = true
|
;(window as any)._enableFillet = true
|
||||||
}
|
}
|
||||||
|
;(window as any).zoomToFit = () =>
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'zoom_to_fit',
|
||||||
|
object_ids: [], // leave empty to zoom to all objects
|
||||||
|
padding: 0.2, // padding around the objects
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -366,7 +366,7 @@ export const modelingMachine = createMachine(
|
|||||||
'Artifact graph emptied': 'hidePlanes',
|
'Artifact graph emptied': 'hidePlanes',
|
||||||
},
|
},
|
||||||
|
|
||||||
entry: 'show default planes',
|
entry: ['show default planes', 'reset camera position'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1063,6 +1063,17 @@ export const modelingMachine = createMachine(
|
|||||||
sketchEnginePathId: '',
|
sketchEnginePathId: '',
|
||||||
sketchPlaneId: '',
|
sketchPlaneId: '',
|
||||||
}),
|
}),
|
||||||
|
'reset camera position': () =>
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
center: { x: 0, y: 0, z: 0 },
|
||||||
|
vantage: { x: 0, y: -1250, z: 580 },
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
}),
|
||||||
'set new sketch metadata': assign((_, { data }) => ({
|
'set new sketch metadata': assign((_, { data }) => ({
|
||||||
sketchDetails: data,
|
sketchDetails: data,
|
||||||
})),
|
})),
|
||||||
@ -1101,11 +1112,13 @@ export const modelingMachine = createMachine(
|
|||||||
store.videoElement?.pause()
|
store.videoElement?.pause()
|
||||||
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||||
focusPath: pathToExtrudeArg,
|
focusPath: pathToExtrudeArg,
|
||||||
zoomToFit: true,
|
// commented out as a part of https://github.com/KittyCAD/modeling-app/issues/3270
|
||||||
zoomOnRangeAndType: {
|
// looking to add back in the future
|
||||||
range: selection.codeBasedSelections[0].range,
|
// zoomToFit: true,
|
||||||
type: 'start_path',
|
// zoomOnRangeAndType: {
|
||||||
},
|
// range: selection.codeBasedSelections[0].range,
|
||||||
|
// type: 'path',
|
||||||
|
// },
|
||||||
})
|
})
|
||||||
if (!engineCommandManager.engineConnection?.idleMode) {
|
if (!engineCommandManager.engineConnection?.idleMode) {
|
||||||
store.videoElement?.play().catch((e) => {
|
store.videoElement?.play().catch((e) => {
|
||||||
|
@ -43,8 +43,8 @@ export default function UserMenu() {
|
|||||||
<h2 className="text-2xl font-bold">User Menu</h2>
|
<h2 className="text-2xl font-bold">User Menu</h2>
|
||||||
<p className="my-4">
|
<p className="my-4">
|
||||||
Click {buttonDescription} in the upper right to open the user menu.
|
Click {buttonDescription} in the upper right to open the user menu.
|
||||||
You can change your user-level settings, sign out, or request a
|
You can change your user-level settings, sign out, report a bug,
|
||||||
feature.
|
manage your account, request a feature, and more.
|
||||||
</p>
|
</p>
|
||||||
<p className="my-4">
|
<p className="my-4">
|
||||||
Many settings can be set either a user or per-project level. User
|
Many settings can be set either a user or per-project level. User
|
||||||
|
@ -2862,7 +2862,7 @@ impl MemberExpression {
|
|||||||
// Actually evaluate memory to compute the property.
|
// Actually evaluate memory to compute the property.
|
||||||
let prop = memory.get(&name, property_src)?;
|
let prop = memory.get(&name, property_src)?;
|
||||||
let MemoryItem::UserVal(prop) = prop else {
|
let MemoryItem::UserVal(prop) = prop else {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
source_ranges: property_sr,
|
source_ranges: property_sr,
|
||||||
message: format!(
|
message: format!(
|
||||||
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
|
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
|
||||||
@ -2876,17 +2876,17 @@ impl MemberExpression {
|
|||||||
.and_then(|x| usize::try_from(x).ok())
|
.and_then(|x| usize::try_from(x).ok())
|
||||||
.map(Property::Number)
|
.map(Property::Number)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
KclError::Syntax(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
source_ranges: property_sr,
|
source_ranges: property_sr,
|
||||||
message: format!(
|
message: format!(
|
||||||
"{name} is not a valid property/index, you can only use a string or int (>= 0) here",
|
"{name}'s value is not a valid property/index, you can only use a string or int (>= 0) here",
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
JValue::String(ref x) => Property::String(x.to_owned()),
|
JValue::String(ref x) => Property::String(x.to_owned()),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
source_ranges: property_sr,
|
source_ranges: property_sr,
|
||||||
message: format!(
|
message: format!(
|
||||||
"{name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array",
|
"{name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array",
|
||||||
@ -2903,7 +2903,7 @@ impl MemberExpression {
|
|||||||
if let Ok(x) = u64::try_from(x) {
|
if let Ok(x) = u64::try_from(x) {
|
||||||
Property::Number(x.try_into().unwrap())
|
Property::Number(x.try_into().unwrap())
|
||||||
} else {
|
} else {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
source_ranges: property_sr,
|
source_ranges: property_sr,
|
||||||
message: format!("{x} is not a valid index, indices must be whole numbers >= 0"),
|
message: format!("{x} is not a valid index, indices must be whole numbers >= 0"),
|
||||||
}));
|
}));
|
||||||
@ -2911,7 +2911,7 @@ impl MemberExpression {
|
|||||||
}
|
}
|
||||||
LiteralValue::String(s) => Property::String(s),
|
LiteralValue::String(s) => Property::String(s),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(KclError::Syntax(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
source_ranges: vec![self.into()],
|
source_ranges: vec![self.into()],
|
||||||
message: "Only strings or ints (>= 0) can be properties/indexes".to_owned(),
|
message: "Only strings or ints (>= 0) can be properties/indexes".to_owned(),
|
||||||
}));
|
}));
|
||||||
@ -2943,7 +2943,7 @@ impl MemberExpression {
|
|||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||||
message: format!("Property {property} not found in object"),
|
message: format!("Property '{property}' not found in object"),
|
||||||
source_ranges: vec![self.clone().into()],
|
source_ranges: vec![self.clone().into()],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -2978,10 +2978,13 @@ impl MemberExpression {
|
|||||||
),
|
),
|
||||||
source_ranges: vec![self.clone().into()],
|
source_ranges: vec![self.clone().into()],
|
||||||
})),
|
})),
|
||||||
(_, _) => Err(KclError::Semantic(KclErrorDetails {
|
(being_indexed, _) => {
|
||||||
message: "Only arrays and objects can be indexed".to_owned(),
|
let t = human_friendly_type(being_indexed);
|
||||||
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Only arrays and objects can be indexed, but you're trying to index a {t}"),
|
||||||
source_ranges: vec![self.clone().into()],
|
source_ranges: vec![self.clone().into()],
|
||||||
})),
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4070,6 +4073,17 @@ impl ConstraintLevels {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn human_friendly_type(j: JValue) -> &'static str {
|
||||||
|
match j {
|
||||||
|
JValue::Null => "null",
|
||||||
|
JValue::Bool(_) => "boolean (true/false value)",
|
||||||
|
JValue::Number(_) => "number",
|
||||||
|
JValue::String(_) => "string (text)",
|
||||||
|
JValue::Array(_) => "array (list)",
|
||||||
|
JValue::Object(_) => "object",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
@ -67,6 +67,27 @@ pub async fn assert_gt(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
args.make_null_user_val()
|
args.make_null_user_val()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that a numerical value equals another at runtime,
|
||||||
|
/// otherwise raise an error.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// let n = 1.0285
|
||||||
|
/// let m = 1.0286
|
||||||
|
/// assertEqual(n, m, 0.01, "n is within the given tolerance for m")
|
||||||
|
/// ```
|
||||||
|
#[stdlib {
|
||||||
|
name = "assertEqual",
|
||||||
|
}]
|
||||||
|
async fn inner_assert_equal(left: f64, right: f64, epsilon: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
||||||
|
_assert((right - left).abs() < epsilon, message, args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn assert_equal(args: Args) -> Result<MemoryItem, KclError> {
|
||||||
|
let (left, right, epsilon, description): (f64, f64, f64, String) = args.get_data()?;
|
||||||
|
inner_assert_equal(left, right, epsilon, &description, &args).await?;
|
||||||
|
args.make_null_user_val()
|
||||||
|
}
|
||||||
|
|
||||||
/// Check that a numerical value is greater than another at runtime,
|
/// Check that a numerical value is greater than another at runtime,
|
||||||
/// otherwise raise an error.
|
/// otherwise raise an error.
|
||||||
///
|
///
|
||||||
|
@ -120,6 +120,7 @@ lazy_static! {
|
|||||||
Box::new(crate::std::math::ToRadians),
|
Box::new(crate::std::math::ToRadians),
|
||||||
Box::new(crate::std::polar::Polar),
|
Box::new(crate::std::polar::Polar),
|
||||||
Box::new(crate::std::assert::Assert),
|
Box::new(crate::std::assert::Assert),
|
||||||
|
Box::new(crate::std::assert::AssertEqual),
|
||||||
Box::new(crate::std::assert::AssertLessThan),
|
Box::new(crate::std::assert::AssertLessThan),
|
||||||
Box::new(crate::std::assert::AssertGreaterThan),
|
Box::new(crate::std::assert::AssertGreaterThan),
|
||||||
Box::new(crate::std::assert::AssertLessThanOrEq),
|
Box::new(crate::std::assert::AssertLessThanOrEq),
|
||||||
|
@ -168,7 +168,9 @@ pub async fn x_line_to(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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)
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// const exampleSketch = startSketchOn('XZ')
|
||||||
@ -214,7 +216,9 @@ pub async fn y_line_to(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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)
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// const exampleSketch = startSketchOn("XZ")
|
||||||
@ -336,7 +340,7 @@ pub async fn x_line(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a line on the x-axis.
|
/// Draw a line parallel to the X-axis, with the given length.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// const exampleSketch = startSketchOn('XZ')
|
||||||
@ -378,7 +382,7 @@ pub async fn y_line(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a line on the y-axis.
|
/// Draw a line parallel to the Y-axis, with the given length.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// const exampleSketch = startSketchOn('XZ')
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
@ -4,8 +4,7 @@ const arr = [0, 0, 0, 10]
|
|||||||
const i = 3
|
const i = 3
|
||||||
const ten = arr[i]
|
const ten = arr[i]
|
||||||
|
|
||||||
assertLessThanOrEq(ten, 10, "oops")
|
assertEqual(ten, 10, 0.000001, "oops")
|
||||||
assertGreaterThanOrEq(ten, 10, "oops2")
|
|
||||||
|
|
||||||
const p = "foo"
|
const p = "foo"
|
||||||
const obj = {
|
const obj = {
|
||||||
@ -14,5 +13,4 @@ const obj = {
|
|||||||
}
|
}
|
||||||
const one = obj[p]
|
const one = obj[p]
|
||||||
|
|
||||||
assertLessThanOrEq(one, 1, "oops")
|
assertEqual(one, 1, 0.0000001, "oops")
|
||||||
assertGreaterThanOrEq(one, 1, "oops2")
|
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
let arr = []
|
||||||
|
let x = arr[0]
|
@ -0,0 +1,18 @@
|
|||||||
|
// This tests indexing an array.
|
||||||
|
|
||||||
|
const array = [90, 91, 92]
|
||||||
|
|
||||||
|
// Test: literal index.
|
||||||
|
|
||||||
|
const result0 = array[1]
|
||||||
|
|
||||||
|
assertLessThanOrEq(result0, 91, "Literal property lookup")
|
||||||
|
assertGreaterThanOrEq(result0, 91, "Literal property lookup")
|
||||||
|
|
||||||
|
// Test: computed index.
|
||||||
|
|
||||||
|
const i = int(1 + 0)
|
||||||
|
const result1 = array[i]
|
||||||
|
|
||||||
|
assertLessThanOrEq(result1, 91, "Computed property lookup")
|
||||||
|
assertGreaterThanOrEq(result1, 91, "Computed property lookup")
|
@ -0,0 +1,2 @@
|
|||||||
|
let arr = [1, 2, 3]
|
||||||
|
let x = arr[1.2]
|
@ -0,0 +1,3 @@
|
|||||||
|
let arr = [1, 2, 3]
|
||||||
|
let i = -1
|
||||||
|
let x = arr[i]
|
@ -0,0 +1,2 @@
|
|||||||
|
let arr = [1, 2, 3]
|
||||||
|
let x = arr["s"]
|
@ -0,0 +1,2 @@
|
|||||||
|
let num = 999
|
||||||
|
let x = num[3]
|
@ -0,0 +1,2 @@
|
|||||||
|
let b = true
|
||||||
|
let x = b["property"]
|
@ -0,0 +1,2 @@
|
|||||||
|
let obj = {key: 123}
|
||||||
|
let num = obj[3]
|
@ -0,0 +1,2 @@
|
|||||||
|
let obj = {}
|
||||||
|
let k = obj["age"]
|
@ -0,0 +1,40 @@
|
|||||||
|
// This tests evaluating properties of objects.
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
foo: 1,
|
||||||
|
bar: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: the property is a literal.
|
||||||
|
|
||||||
|
const one_a = obj["foo"]
|
||||||
|
|
||||||
|
assertLessThanOrEq(one_a, 1, "Literal property lookup")
|
||||||
|
assertGreaterThanOrEq(one_a, 1, "Literal property lookup")
|
||||||
|
|
||||||
|
// Test: the property is a variable,
|
||||||
|
// which must be evaluated before looking it up.
|
||||||
|
|
||||||
|
const p = "foo"
|
||||||
|
const one_b = obj[p]
|
||||||
|
|
||||||
|
assertLessThanOrEq(one_b, 1, "Computed property lookup")
|
||||||
|
assertGreaterThanOrEq(one_b, 1, "Computed property lookup")
|
||||||
|
|
||||||
|
// Test: multiple literal properties.
|
||||||
|
|
||||||
|
const obj2 = {
|
||||||
|
inner: obj,
|
||||||
|
}
|
||||||
|
|
||||||
|
const one_c = obj2.inner["foo"]
|
||||||
|
|
||||||
|
assertLessThanOrEq(one_c, 1, "Literal property lookup")
|
||||||
|
assertGreaterThanOrEq(one_c, 1, "Literal property lookup")
|
||||||
|
|
||||||
|
// Test: multiple properties, mix of literal and computed.
|
||||||
|
|
||||||
|
const one_d = obj2.inner[p]
|
||||||
|
|
||||||
|
assertLessThanOrEq(one_d, 1, "Computed property lookup")
|
||||||
|
assertGreaterThanOrEq(one_d, 1, "Computed property lookup")
|
@ -4,7 +4,7 @@ use kcl_lib::{settings::types::UnitLength, test_server::execute_and_snapshot};
|
|||||||
/// i.e. how different the current model snapshot can be from the previous saved one.
|
/// i.e. how different the current model snapshot can be from the previous saved one.
|
||||||
const MIN_DIFF: f64 = 0.99;
|
const MIN_DIFF: f64 = 0.99;
|
||||||
|
|
||||||
// mod server;
|
mod no_visuals;
|
||||||
|
|
||||||
macro_rules! kcl_input {
|
macro_rules! kcl_input {
|
||||||
($file:literal) => {
|
($file:literal) => {
|
||||||
|
87
src/wasm-lib/tests/executor/no_visuals.rs
Normal file
87
src/wasm-lib/tests/executor/no_visuals.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use kcl_lib::{ast::types::Program, errors::KclError, executor::ExecutorContext};
|
||||||
|
|
||||||
|
macro_rules! gen_test {
|
||||||
|
($file:ident) => {
|
||||||
|
#[tokio::test]
|
||||||
|
async fn $file() {
|
||||||
|
let code = include_str!(concat!("inputs/no_visuals/", stringify!($file), ".kcl"));
|
||||||
|
run(&code).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! gen_test_fail {
|
||||||
|
($file:ident, $expected:literal) => {
|
||||||
|
#[tokio::test]
|
||||||
|
async fn $file() {
|
||||||
|
let code = include_str!(concat!("inputs/no_visuals/", stringify!($file), ".kcl"));
|
||||||
|
let actual = run_fail(&code).await;
|
||||||
|
assert_eq!(actual.get_message(), $expected);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(code: &str) {
|
||||||
|
let (ctx, program) = setup(code).await;
|
||||||
|
|
||||||
|
ctx.run(&program, None).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup(program: &str) -> (ExecutorContext, Program) {
|
||||||
|
let tokens = kcl_lib::token::lexer(program).unwrap();
|
||||||
|
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast().unwrap();
|
||||||
|
let ctx = kcl_lib::executor::ExecutorContext {
|
||||||
|
engine: std::sync::Arc::new(Box::new(
|
||||||
|
kcl_lib::engine::conn_mock::EngineConnection::new().await.unwrap(),
|
||||||
|
)),
|
||||||
|
fs: std::sync::Arc::new(kcl_lib::fs::FileManager::new()),
|
||||||
|
stdlib: std::sync::Arc::new(kcl_lib::std::StdLib::new()),
|
||||||
|
settings: Default::default(),
|
||||||
|
is_mock: true,
|
||||||
|
};
|
||||||
|
(ctx, program)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_fail(code: &str) -> KclError {
|
||||||
|
let (ctx, program) = setup(code).await;
|
||||||
|
let Err(e) = ctx.run(&program, None).await else {
|
||||||
|
panic!("Expected this KCL program to fail, but it (incorrectly) never threw an error.");
|
||||||
|
};
|
||||||
|
e
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_test!(property_of_object);
|
||||||
|
gen_test!(index_of_array);
|
||||||
|
gen_test_fail!(
|
||||||
|
invalid_index_str,
|
||||||
|
"semantic: Only integers >= 0 can be used as the index of an array, but you're using a string"
|
||||||
|
);
|
||||||
|
gen_test_fail!(
|
||||||
|
invalid_index_negative,
|
||||||
|
"semantic: i's value is not a valid property/index, you can only use a string or int (>= 0) here"
|
||||||
|
);
|
||||||
|
gen_test_fail!(
|
||||||
|
invalid_index_fractional,
|
||||||
|
"semantic: Only strings or ints (>= 0) can be properties/indexes"
|
||||||
|
);
|
||||||
|
gen_test_fail!(
|
||||||
|
invalid_member_object,
|
||||||
|
"semantic: Only arrays and objects can be indexed, but you're trying to index a number"
|
||||||
|
);
|
||||||
|
gen_test_fail!(
|
||||||
|
invalid_member_object_prop,
|
||||||
|
"semantic: Only arrays and objects can be indexed, but you're trying to index a boolean (true/false value)"
|
||||||
|
);
|
||||||
|
gen_test_fail!(
|
||||||
|
non_string_key_of_object,
|
||||||
|
"semantic: Only strings can be used as the property of an object, but you're using a number"
|
||||||
|
);
|
||||||
|
gen_test_fail!(
|
||||||
|
array_index_oob,
|
||||||
|
"undefined value: The array doesn't have any item at index 0"
|
||||||
|
);
|
||||||
|
gen_test_fail!(
|
||||||
|
object_prop_not_found,
|
||||||
|
"undefined value: Property 'age' not found in object"
|
||||||
|
);
|
Reference in New Issue
Block a user