Compare commits
145 Commits
codex/upda
...
nicboone8-
Author | SHA1 | Date | |
---|---|---|---|
a831dcefb4 | |||
bc6a8ccb78 | |||
4390788f17 | |||
2ddd546123 | |||
fd3125e641 | |||
402de8c926 | |||
75dff9f775 | |||
5f6d810fbb | |||
55e1ec7dad | |||
b123dacc41 | |||
5911df46ee | |||
ec19b9bfaa | |||
962609e3d3 | |||
87e3588ceb | |||
c4d2e33a99 | |||
2ac05508bc | |||
5c6d4fbf5a | |||
aaff027830 | |||
355a450c09 | |||
21f4fcb041 | |||
37aea72a88 | |||
783b6ed76c | |||
9dfb67cf61 | |||
889c72ec60 | |||
067e193780 | |||
77730196ae | |||
dba0173cc3 | |||
8f4327ab6b | |||
cc2769e907 | |||
91b6db0ba5 | |||
8bae76000c | |||
f502e445cc | |||
083bfe6ec2 | |||
2c1a5ff5c4 | |||
0c2785df67 | |||
fa9d5a0104 | |||
678433d2b3 | |||
30bd307931 | |||
08dfaba7f7 | |||
eb2327827b | |||
1f53dd1357 | |||
034366e65e | |||
cd537cd9c2 | |||
22f92942f6 | |||
4eee50d79e | |||
125b2c44d4 | |||
db9e35d686 | |||
d0958220fe | |||
fa4b3cfd1b | |||
8972f8f109 | |||
85ccc6900c | |||
4e2deca5d8 | |||
04a2c184d7 | |||
2c7701e2d4 | |||
ae569b61db | |||
0a0e6abd3f | |||
9c7aee32bd | |||
ed979d807b | |||
f5c244dbb1 | |||
0ea1e9a6da | |||
a36530d6df | |||
825d34718a | |||
d90d445d84 | |||
5976a0cba6 | |||
b50f2f5a2a | |||
f877b52898 | |||
4a585db637 | |||
4d404bf137 | |||
e644b7e1fc | |||
aff1684064 | |||
d9afc50f91 | |||
eb7b4ccda6 | |||
bbf4f1d251 | |||
3cc7859ca5 | |||
ab63345c57 | |||
3df02e02fa | |||
35f5c62633 | |||
0f0fc39d07 | |||
a13b6b2b70 | |||
4212b95232 | |||
38a73a603b | |||
c48d9fd4d7 | |||
0753987b5a | |||
815ff7dc2b | |||
46684d420d | |||
eca09984a3 | |||
ce63c6423e | |||
09699afe82 | |||
36c8ad439d | |||
5dc77ceed5 | |||
c7baa26b2d | |||
4d0454abcd | |||
1dafbf105e | |||
773f013115 | |||
c5cd460595 | |||
845352046b | |||
597f1087f9 | |||
511334683a | |||
223a4ad45d | |||
edf31ec1d3 | |||
1539557005 | |||
1d3ba4e3ac | |||
4110aa00db | |||
7eb52cda36 | |||
7872fb9cbd | |||
651181e62c | |||
38a245f2fc | |||
1b4289f93f | |||
d0697c24fd | |||
8c24e29081 | |||
2b9d26e2ff | |||
ab148a7654 | |||
553e650fbe | |||
9690a24c68 | |||
978d5d44a2 | |||
9df476543a | |||
cf303ebe97 | |||
b1d1d89ca5 | |||
3a599d0a0a | |||
8340f6b906 | |||
ddb034b14d | |||
bfa2f67393 | |||
447069a97b | |||
49b78d726a | |||
5b4cddd0b0 | |||
8878c148ed | |||
c3c2ded795 | |||
fb35fdcc38 | |||
e76ba9921c | |||
b19acd550d | |||
f3e9d110c0 | |||
658497da1d | |||
bd01059a92 | |||
57a977e6be | |||
94b0cc1f3e | |||
5734cc7fc3 | |||
3168c22de7 | |||
3c94fe9047 | |||
cdd6b56d42 | |||
75ac3bc61b | |||
29d511d085 | |||
b0a41939e8 | |||
7d2c1061ba | |||
d768073d17 | |||
dc8496c62e |
12
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -7,11 +7,11 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
|
|||||||
# If no last run artifact, than run Playwright normally
|
# If no last run artifact, than run Playwright normally
|
||||||
echo "run playwright normally"
|
echo "run playwright normally"
|
||||||
if [[ "$3" == *ubuntu* ]]; then
|
if [[ "$3" == *ubuntu* ]]; then
|
||||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:playwright:electron -- --shard=$1/$2 || true
|
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:e2e:desktop -- --shard=$1/$2 || true
|
||||||
elif [[ "$3" == *windows* ]]; then
|
elif [[ "$3" == *windows* ]]; then
|
||||||
npm run test:playwright:electron -- --grep=@windows --shard=$1/$2 || true
|
npm run test:e2e:desktop -- --grep=@windows --shard=$1/$2 || true
|
||||||
elif [[ "$3" == *macos* ]]; then
|
elif [[ "$3" == *macos* ]]; then
|
||||||
npm run test:playwright:electron -- --grep=@macos --shard=$1/$2 || true
|
npm run test:e2e:desktop -- --grep=@macos --shard=$1/$2 || true
|
||||||
else
|
else
|
||||||
echo "Do not run Playwright. Unable to detect os runtime."
|
echo "Do not run Playwright. Unable to detect os runtime."
|
||||||
exit 1
|
exit 1
|
||||||
@ -31,11 +31,11 @@ while [[ $retry -le $max_retries ]]; do
|
|||||||
echo "retried=true" >>$GITHUB_OUTPUT
|
echo "retried=true" >>$GITHUB_OUTPUT
|
||||||
echo "run playwright with last failed tests and retry $retry"
|
echo "run playwright with last failed tests and retry $retry"
|
||||||
if [[ "$3" == *ubuntu* ]]; then
|
if [[ "$3" == *ubuntu* ]]; then
|
||||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:playwright:electron -- --last-failed || true
|
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:e2e:desktop -- --last-failed || true
|
||||||
elif [[ "$3" == *windows* ]]; then
|
elif [[ "$3" == *windows* ]]; then
|
||||||
npm run test:playwright:electron -- --grep=@windows --last-failed || true
|
npm run test:e2e:desktop -- --grep=@windows --last-failed || true
|
||||||
elif [[ "$3" == *macos* ]]; then
|
elif [[ "$3" == *macos* ]]; then
|
||||||
npm run test:playwright:electron -- --grep=@macos --last-failed || true
|
npm run test:e2e:desktop -- --grep=@macos --last-failed || true
|
||||||
else
|
else
|
||||||
echo "Do not run playwright. Unable to detect os runtime."
|
echo "Do not run playwright. Unable to detect os runtime."
|
||||||
exit 1
|
exit 1
|
||||||
|
2
.github/ci-cd-scripts/upload-results.sh
vendored
@ -6,6 +6,7 @@ if [ -z "${TAB_API_URL:-}" ] || [ -z "${TAB_API_KEY:-}" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
project="https://github.com/KittyCAD/modeling-app"
|
project="https://github.com/KittyCAD/modeling-app"
|
||||||
|
suite="${CI_SUITE:-unit}"
|
||||||
branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
|
branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
|
||||||
commit="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
|
commit="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ echo "Uploading batch results"
|
|||||||
curl --silent --request POST \
|
curl --silent --request POST \
|
||||||
--header "X-API-Key: ${TAB_API_KEY}" \
|
--header "X-API-Key: ${TAB_API_KEY}" \
|
||||||
--form "project=${project}" \
|
--form "project=${project}" \
|
||||||
|
--form "suite=${suite}" \
|
||||||
--form "branch=${branch}" \
|
--form "branch=${branch}" \
|
||||||
--form "commit=${commit}" \
|
--form "commit=${commit}" \
|
||||||
--form "tests=@test-results/junit.xml" \
|
--form "tests=@test-results/junit.xml" \
|
||||||
|
13
.github/workflows/cargo-test.yml
vendored
@ -1,16 +1,22 @@
|
|||||||
|
name: cargo test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: 0 * * * * # hourly
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
name: cargo test
|
|
||||||
jobs:
|
jobs:
|
||||||
build-test-artifacts:
|
build-test-artifacts:
|
||||||
name: Build test artifacts
|
name: Build test artifacts
|
||||||
@ -88,6 +94,7 @@ jobs:
|
|||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||||
ZOO_HOST: https://api.dev.zoo.dev
|
ZOO_HOST: https://api.dev.zoo.dev
|
||||||
RUST_BACKTRACE: full
|
RUST_BACKTRACE: full
|
||||||
|
RUST_MIN_STACK: 10485760000
|
||||||
- name: Commit differences
|
- name: Commit differences
|
||||||
if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure'
|
if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure'
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -119,6 +126,7 @@ jobs:
|
|||||||
# Configure nextest when it's run by insta (via just).
|
# Configure nextest when it's run by insta (via just).
|
||||||
NEXTEST_PROFILE: ci
|
NEXTEST_PROFILE: ci
|
||||||
RUST_BACKTRACE: full
|
RUST_BACKTRACE: full
|
||||||
|
RUST_MIN_STACK: 10485760000
|
||||||
- name: Build and archive tests
|
- name: Build and archive tests
|
||||||
run: |
|
run: |
|
||||||
cd rust
|
cd rust
|
||||||
@ -182,6 +190,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||||
ZOO_HOST: https://api.dev.zoo.dev
|
ZOO_HOST: https://api.dev.zoo.dev
|
||||||
|
RUST_MIN_STACK: 10485760000
|
||||||
- name: Upload results
|
- name: Upload results
|
||||||
if: always()
|
if: always()
|
||||||
run: .github/ci-cd-scripts/upload-results.sh
|
run: .github/ci-cd-scripts/upload-results.sh
|
||||||
@ -190,6 +199,7 @@ jobs:
|
|||||||
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||||
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
CI_SUITE: e2e:kcl
|
||||||
run-internal-kcl-samples:
|
run-internal-kcl-samples:
|
||||||
name: cargo test (internal-kcl-samples)
|
name: cargo test (internal-kcl-samples)
|
||||||
runs-on:
|
runs-on:
|
||||||
@ -238,6 +248,7 @@ jobs:
|
|||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||||
ZOO_HOST: https://api.dev.zoo.dev
|
ZOO_HOST: https://api.dev.zoo.dev
|
||||||
MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}}
|
MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}}
|
||||||
|
RUST_MIN_STACK: 10485760000
|
||||||
run-wasm-tests:
|
run-wasm-tests:
|
||||||
name: Run wasm tests
|
name: Run wasm tests
|
||||||
strategy:
|
strategy:
|
||||||
|
136
.github/workflows/e2e-tests.yml
vendored
@ -1,4 +1,5 @@
|
|||||||
name: E2E Tests
|
name: E2E Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@ -19,9 +20,11 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
prepare-wasm:
|
prepare-wasm:
|
||||||
|
|
||||||
# separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
|
# separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
|
||||||
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
|
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- id: filter
|
- id: filter
|
||||||
@ -99,10 +102,13 @@ jobs:
|
|||||||
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
|
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
name: playwright:snapshots:ubuntu
|
|
||||||
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
|
|
||||||
needs: [prepare-wasm]
|
needs: [prepare-wasm]
|
||||||
|
|
||||||
|
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
|
||||||
|
name: e2e:snapshots
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/create-github-app-token@v1
|
- uses: actions/create-github-app-token@v1
|
||||||
id: app-token
|
id: app-token
|
||||||
with:
|
with:
|
||||||
@ -130,10 +136,9 @@ jobs:
|
|||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
id: deps-install
|
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
- name: Cache browsers
|
- name: Download browser cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@ -143,7 +148,7 @@ jobs:
|
|||||||
- name: Install browsers
|
- name: Install browsers
|
||||||
run: npm run playwright install --with-deps
|
run: npm run playwright install --with-deps
|
||||||
|
|
||||||
- name: Capture snapshots
|
- name: npm run test:snapshots
|
||||||
uses: nick-fields/retry@v3.0.2
|
uses: nick-fields/retry@v3.0.2
|
||||||
with:
|
with:
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -156,6 +161,19 @@ jobs:
|
|||||||
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||||
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
CI_SUITE: e2e:snapshots
|
||||||
|
TARGET: web
|
||||||
|
|
||||||
|
- name: Update snapshots
|
||||||
|
if: always()
|
||||||
|
run: npm run test:snapshots -- --last-failed --update-snapshots
|
||||||
|
env:
|
||||||
|
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
|
TAB_API_URL: ${{ secrets.TAB_API_URL }}
|
||||||
|
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||||
|
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
CI_SUITE: e2e:snapshots
|
||||||
TARGET: web
|
TARGET: web
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
@ -173,7 +191,7 @@ jobs:
|
|||||||
id: git-check
|
id: git-check
|
||||||
run: |
|
run: |
|
||||||
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
|
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
|
||||||
if git status | grep -q "Changes to be committed"
|
if git status | grep --quiet "Changes to be committed"
|
||||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
@ -193,12 +211,93 @@ jobs:
|
|||||||
git push
|
git push
|
||||||
git push origin ${{ github.head_ref }}
|
git push origin ${{ github.head_ref }}
|
||||||
|
|
||||||
electron:
|
web:
|
||||||
needs: [prepare-wasm]
|
needs: [prepare-wasm]
|
||||||
timeout-minutes: 60
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
name: e2e:web (${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }})
|
||||||
env:
|
env:
|
||||||
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
|
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
|
||||||
name: playwright:electron:${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }} (shard ${{ matrix.shardIndex }})
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/create-github-app-token@v1
|
||||||
|
id: app-token
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
|
||||||
|
owner: ${{ github.repository_owner }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
name: prepared-wasm
|
||||||
|
|
||||||
|
- name: Copy prepared Wasm
|
||||||
|
run: |
|
||||||
|
ls -R prepared-wasm
|
||||||
|
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
|
||||||
|
mkdir rust/kcl-wasm-lib/pkg
|
||||||
|
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Download browser cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/ms-playwright/
|
||||||
|
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
|
||||||
|
|
||||||
|
- name: Install browsers
|
||||||
|
run: npm run playwright install --with-deps
|
||||||
|
|
||||||
|
- name: Start Vector
|
||||||
|
if: ${{ !contains(matrix.os, 'windows') }}
|
||||||
|
run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
|
||||||
|
env:
|
||||||
|
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
|
||||||
|
OS_NAME: ${{ env.OS_NAME }}
|
||||||
|
|
||||||
|
- name: npm run test:e2e:web
|
||||||
|
uses: nick-fields/retry@v3.0.2
|
||||||
|
with:
|
||||||
|
shell: bash
|
||||||
|
command: npm run test:e2e:web
|
||||||
|
timeout_minutes: 5
|
||||||
|
max_attempts: 5
|
||||||
|
env:
|
||||||
|
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
|
TAB_API_URL: ${{ secrets.TAB_API_URL }}
|
||||||
|
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||||
|
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
CI_SUITE: e2e:web
|
||||||
|
TARGET: web
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ !cancelled() && (success() || failure()) }}
|
||||||
|
with:
|
||||||
|
path: playwright-report/
|
||||||
|
include-hidden-files: true
|
||||||
|
retention-days: 30
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
desktop:
|
||||||
|
needs: [prepare-wasm]
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -241,13 +340,17 @@ jobs:
|
|||||||
shardIndex: 2
|
shardIndex: 2
|
||||||
shardTotal: 2
|
shardTotal: 2
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
name: e2e:desktop (${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}, shard ${{ matrix.shardIndex }})
|
||||||
|
env:
|
||||||
|
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
name: prepared-wasm
|
name: prepared-wasm
|
||||||
|
|
||||||
- name: Copy prepared wasm
|
- name: Copy prepared Wasm
|
||||||
run: |
|
run: |
|
||||||
ls -R prepared-wasm
|
ls -R prepared-wasm
|
||||||
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
|
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
|
||||||
@ -263,19 +366,16 @@ jobs:
|
|||||||
id: deps-install
|
id: deps-install
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
- name: Cache Playwright Browsers
|
- name: Download browser cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.cache/ms-playwright/
|
~/.cache/ms-playwright/
|
||||||
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
|
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
|
||||||
|
|
||||||
- name: Install Playwright Browsers
|
- name: Install browsers
|
||||||
run: npm run playwright install --with-deps
|
run: npm run playwright install --with-deps
|
||||||
|
|
||||||
- name: Build web
|
|
||||||
run: npm run tronb:vite:dev
|
|
||||||
|
|
||||||
- name: Start Vector
|
- name: Start Vector
|
||||||
if: ${{ !contains(matrix.os, 'windows') }}
|
if: ${{ !contains(matrix.os, 'windows') }}
|
||||||
run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
|
run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
|
||||||
@ -283,6 +383,9 @@ jobs:
|
|||||||
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
|
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
|
||||||
OS_NAME: ${{ env.OS_NAME }}
|
OS_NAME: ${{ env.OS_NAME }}
|
||||||
|
|
||||||
|
- name: Build app
|
||||||
|
run: npm run tronb:vite:dev
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
if: ${{ !cancelled() && (success() || failure()) }}
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@ -290,7 +393,7 @@ jobs:
|
|||||||
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
|
|
||||||
- name: Run playwright/electron flow (with retries)
|
- name: npm run test:e2e:desktop
|
||||||
id: retry
|
id: retry
|
||||||
if: ${{ !cancelled() && steps.deps-install.outcome == 'success' }}
|
if: ${{ !cancelled() && steps.deps-install.outcome == 'success' }}
|
||||||
uses: nick-fields/retry@v3.0.2
|
uses: nick-fields/retry@v3.0.2
|
||||||
@ -306,6 +409,7 @@ jobs:
|
|||||||
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||||
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
CI_SUITE: e2e:desktop
|
||||||
TARGET: desktop
|
TARGET: desktop
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
@ -122,25 +122,24 @@ https://github.com/KittyCAD/modeling-app/issues/new
|
|||||||
|
|
||||||
#### 2. Push a new tag
|
#### 2. Push a new tag
|
||||||
|
|
||||||
Create a new tag and push it to the repo. The `semantic-release.sh` script will automatically bump the minor part, which we use the most. For instance going from `v0.27.0` to `v0.28.0`.
|
Decide on a `v`-prefixed semver `VERSION` (e.g. `v1.2.3`) with the team and tag the repo on the latest main:
|
||||||
|
|
||||||
```
|
```
|
||||||
VERSION=$(./scripts/semantic-release.sh)
|
git tag $VERSION --message=""
|
||||||
git tag $VERSION
|
git push origin $VERSION
|
||||||
git push origin --tags
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files.
|
This will trigger the `build-apps` workflow to set the version, build & sign the apps, and generate release files.
|
||||||
|
|
||||||
The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush)).
|
The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush).
|
||||||
|
|
||||||
#### 3. Manually test artifacts
|
#### 3. Manually test artifacts
|
||||||
|
|
||||||
##### Release builds
|
##### Release builds
|
||||||
|
|
||||||
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in 2.).
|
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in step 2).
|
||||||
|
|
||||||
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
|
Manually test against [this list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
|
||||||
|
|
||||||
A prompt should show up asking for a downgrade to the last release version. Running through that at the end of testing
|
A prompt should show up asking for a downgrade to the last release version. Running through that at the end of testing
|
||||||
and making sure the current release candidate has the ability to be updated to what electron-updater points to is critical,
|
and making sure the current release candidate has the ability to be updated to what electron-updater points to is critical,
|
||||||
@ -158,15 +157,20 @@ If the prompt doesn't show up, start the app in command line to grab the electro
|
|||||||
./Zoo Design Studio-{version}-{arch}-linux.AppImage
|
./Zoo Design Studio-{version}-{arch}-linux.AppImage
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4. Publish the release
|
#### 4. Bump the KCL version
|
||||||
|
|
||||||
Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the _Release title_ field as well.
|
Follow the instructions [here](./rust/README.md) to publish new crates.
|
||||||
|
This ensures that the KCL accepted by the app is also accepted by the CLI.
|
||||||
|
|
||||||
Hit _Generate release notes_ as a starting point to discuss the changelog in the issue. Once done, make sure _Set as the latest release_ is checked, and hit _Publish release_.
|
#### 5. Publish the release
|
||||||
|
|
||||||
A new `publish-apps-release` will kick in and you should be able to find it [here](https://github.com/KittyCAD/modeling-app/actions?query=event%3Arelease). On success, the files will be uploaded to the public bucket as well as to the GitHub release, and the announcement on Discord will be sent.
|
Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the **Release title** field as well.
|
||||||
|
|
||||||
#### 5. Close the issue
|
Click **Generate release notes** as a starting point to discuss the changelog in the issue. Once done, make sure **Set as the latest release** is checked, and click **Publish release**.
|
||||||
|
|
||||||
|
A new `publish-apps-release` workflow will start and you should be able to find it [here](https://github.com/KittyCAD/modeling-app/actions?query=event%3Arelease). On success, the files will be uploaded to the public bucket as well as to the GitHub release, and the announcement on Discord will be sent.
|
||||||
|
|
||||||
|
#### 6. Close the issue
|
||||||
|
|
||||||
If everything is well and the release is out to the public, the issue tracking the release shall be closed.
|
If everything is well and the release is out to the public, the issue tracking the release shall be closed.
|
||||||
|
|
||||||
@ -201,7 +205,7 @@ Prepare these system dependencies:
|
|||||||
|
|
||||||
#### Snapshot tests (Google Chrome on Ubuntu only)
|
#### Snapshot tests (Google Chrome on Ubuntu only)
|
||||||
|
|
||||||
Only Ubunu and Google Chrome is supported for the set of tests evaluating screenshot snapshots.
|
Only Ubuntu and Google Chrome is supported for the set of tests evaluating screenshot snapshots.
|
||||||
If you don't run Ubuntu locally or in a VM, you may use a GitHub Codespace.
|
If you don't run Ubuntu locally or in a VM, you may use a GitHub Codespace.
|
||||||
```
|
```
|
||||||
npm run playwright -- install chrome
|
npm run playwright -- install chrome
|
||||||
@ -209,14 +213,21 @@ npm run test:snapshots
|
|||||||
```
|
```
|
||||||
You may use `-- --update-snapshots` as needed.
|
You may use `-- --update-snapshots` as needed.
|
||||||
|
|
||||||
#### Electron flow tests (Chromium on Ubuntu, macOS, Windows)
|
#### Desktop tests (Electron on all platforms)
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run playwright -- install chromium
|
npm run playwright -- install chromium
|
||||||
npm run test:playwright:electron:local
|
npm run test:e2e:desktop:local
|
||||||
```
|
```
|
||||||
|
|
||||||
You may use `-- -g "my test"` to match specific test titles, or `-- path/to/file.spec.ts` for a test file.
|
You may use `-- -g "my test"` to match specific test titles, or `-- path/to/file.spec.ts` for a test file.
|
||||||
|
|
||||||
|
#### Web tests (Google Chrome on all platforms)
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run test:e2e:web
|
||||||
|
```
|
||||||
|
|
||||||
#### Debugger
|
#### Debugger
|
||||||
|
|
||||||
However, if you want a debugger I recommend using VSCode and the `playwright` extension, as the above command is a cruder debugger that steps into every function call which is annoying.
|
However, if you want a debugger I recommend using VSCode and the `playwright` extension, as the above command is a cruder debugger that steps into every function call which is annoying.
|
||||||
|
10
INSTALL.md
@ -4,7 +4,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
|
|||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
|
1. Download the [Zoo Design Studio installer](https://zoo.dev/design-studio/download) for Windows and for your processor type.
|
||||||
|
|
||||||
2. Once downloaded, run the installer `Zoo Design Studio-{version}-{arch}-win.exe` which should take a few seconds.
|
2. Once downloaded, run the installer `Zoo Design Studio-{version}-{arch}-win.exe` which should take a few seconds.
|
||||||
|
|
||||||
@ -12,16 +12,16 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
|
|||||||
|
|
||||||
## macOS
|
## macOS
|
||||||
|
|
||||||
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
|
1. Download the [Zoo Design Studio installer](https://zoo.dev/design-studio/download) for macOS and for your processor type.
|
||||||
|
|
||||||
2. Once downloaded, open the disk image `Zoo Design Studio-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
|
2. Once downloaded, open the disk image `Zoo Design Studio-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
|
||||||
|
|
||||||
3. You can then open your `Applications` directory and double-click on `Zoo Design Studio` to open.
|
3. You can then open your `Applications` directory and double-click on `Zoo Design Studio` to open.
|
||||||
|
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
|
1. Download the [Zoo Design Studio installer](https://zoo.dev/design-studio/download) for Linux and for your processor type.
|
||||||
|
|
||||||
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
|
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
|
||||||
- On Ubuntu, install the FUSE library with these commands in a terminal.
|
- On Ubuntu, install the FUSE library with these commands in a terminal.
|
||||||
@ -29,7 +29,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
|
|||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install libfuse2
|
sudo apt install libfuse2
|
||||||
```
|
```
|
||||||
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
|
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
|
||||||
- Once installed, copy the downloaded `Zoo Design Studio-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
|
- Once installed, copy the downloaded `Zoo Design Studio-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
|
||||||
|
|
||||||
- `appimaged` should automatically find it and make it executable. If not, run:
|
- `appimaged` should automatically find it and make it executable. If not, run:
|
||||||
|
9
Makefile
@ -120,19 +120,18 @@ test-e2e: test-e2e-$(TARGET)
|
|||||||
|
|
||||||
.PHONY: test-e2e-web
|
.PHONY: test-e2e-web
|
||||||
test-e2e-web: install build ## Run the web e2e tests
|
test-e2e-web: install build ## Run the web e2e tests
|
||||||
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
|
|
||||||
ifdef E2E_GREP
|
ifdef E2E_GREP
|
||||||
npm run chrome:test -- --headed --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
|
npm run test:e2e:web -- --headed --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
|
||||||
else
|
else
|
||||||
npm run chrome:test -- --headed --workers='100%'
|
npm run test:e2e:web -- --headed --workers='100%'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: test-e2e-desktop
|
.PHONY: test-e2e-desktop
|
||||||
test-e2e-desktop: install build ## Run the desktop e2e tests
|
test-e2e-desktop: install build ## Run the desktop e2e tests
|
||||||
ifdef E2E_GREP
|
ifdef E2E_GREP
|
||||||
npm run test:playwright:electron -- --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
|
npm run test:e2e:desktop -- --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
|
||||||
else
|
else
|
||||||
npm run test:playwright:electron -- --workers='100%'
|
npm run test:e2e:desktop -- --workers='100%'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
12
README.md
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Zoo Design Studio
|
# Zoo Design Studio
|
||||||
|
|
||||||
[zoo.dev/modeling-app](https://zoo.dev/modeling-app)
|
[zoo.dev/design-studio](https://zoo.dev/design-studio)
|
||||||
|
|
||||||
A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
|
A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
|
||||||
|
|
||||||
@ -40,14 +40,8 @@ The 3D view in Design Studio is just a video stream from our hosted geometry eng
|
|||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
We recommend downloading the latest application binary from our [releases](https://github.com/KittyCAD/modeling-app/releases) page. If you don't see your platform or architecture supported there, please file an issue.
|
We recommend downloading the latest application binary from our [website](https://zoo.dev/design-studio/download). If you don't see your platform or architecture supported there, please file an issue. See the [installation guide](INSTALL.md) for additional instructions.
|
||||||
|
|
||||||
If you'd like to try out upcoming changes sooner, you can also download those from our [nightly releases](https://zoo.dev/modeling-app/download/nightly) page.
|
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started.
|
Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started. To contribute to the KittyCAD Language, see the dedicated [readme](rust/kcl-lib/README.md) for KCL.
|
||||||
|
|
||||||
## KCL
|
|
||||||
|
|
||||||
To contribute to the KittyCAD Language, see the [README](https://github.com/KittyCAD/modeling-app/tree/main/rust/kcl-lib) for KCL.
|
|
||||||
|
@ -16,15 +16,16 @@ There are some useful functions for working with arrays in the standard library,
|
|||||||
|
|
||||||
Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values.
|
Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values.
|
||||||
|
|
||||||
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; 1+]` denotes an array whose length is at least one (i.e., a non-empty array). E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
|
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; n+]` denotes an array whose length is at least `n`. The common case for that is `[T; 1+]`, i.e., a non-empty array. E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
|
||||||
|
|
||||||
## Ranges
|
## Ranges
|
||||||
|
|
||||||
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. Examples:
|
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. A range which is exclusive of its end is written with `<end`. Examples:
|
||||||
|
|
||||||
```kcl,norun
|
```kcl,norun
|
||||||
[0..3] // [0, 1, 2, 3]
|
[0..3] // [0, 1, 2, 3]
|
||||||
[3..10] // [3, 4, 5, 6, 7, 8, 9, 10]
|
[3..10] // [3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
[3..<10] // [3, 4, 5, 6, 7, 8, 9]
|
||||||
x = 2
|
x = 2
|
||||||
[x..x+1] // [2, 3]
|
[x..x+1] // [2, 3]
|
||||||
```
|
```
|
||||||
|
@ -4,7 +4,7 @@ excerpt: "Documentation of the KCL language for the Zoo Design Studio."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains
|
This is a reference for KCL. If you are learning KCL, you may prefer the [guide](https://zoo.dev/docs/kcl-book/intro.html) which explains
|
||||||
things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std).
|
things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std).
|
||||||
|
|
||||||
## Topics
|
## Topics
|
||||||
|
@ -27,9 +27,6 @@ import increment from "util.kcl"
|
|||||||
answer = increment(41)
|
answer = increment(41)
|
||||||
```
|
```
|
||||||
|
|
||||||
Imported files _must_ be in the same project so that units are uniform across
|
|
||||||
modules. This means that it must be in the same directory.
|
|
||||||
|
|
||||||
Import statements must be at the top-level of a file. It is not allowed to have
|
Import statements must be at the top-level of a file. It is not allowed to have
|
||||||
an `import` statement inside a function or in the body of an if‑else.
|
an `import` statement inside a function or in the body of an if‑else.
|
||||||
|
|
||||||
@ -58,6 +55,9 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
|
|||||||
import increment as inc, decrement as dec from "util.kcl"
|
import increment as inc, decrement as dec from "util.kcl"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can import files from the current directory or from subdirectories, but if importing from a
|
||||||
|
subdirectory you can only import `main.kcl`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Functions vs `clone`
|
## Functions vs `clone`
|
||||||
@ -177,7 +177,7 @@ You can also import the whole module. This is useful if you want to use the
|
|||||||
result of a module as a variable, like a part.
|
result of a module as a variable, like a part.
|
||||||
|
|
||||||
```norun
|
```norun
|
||||||
import "tests/inputs/cube.kcl" as cube
|
import "cube.kcl"
|
||||||
cube
|
cube
|
||||||
|> translate(x=10)
|
|> translate(x=10)
|
||||||
```
|
```
|
||||||
@ -229,6 +229,19 @@ The final statement is what's important because it's the return value of the
|
|||||||
entire module. The module is expected to return a single object that can be used
|
entire module. The module is expected to return a single object that can be used
|
||||||
as a variable by the file that imports it.
|
as a variable by the file that imports it.
|
||||||
|
|
||||||
|
The name of the file or subdirectory is used as the name of the variable within the importing program.
|
||||||
|
If you want to use a different name, you can do so by using the `as` keyword:
|
||||||
|
|
||||||
|
```kcl,norun
|
||||||
|
import "cube.kcl" // Introduces a new variable called `cube`.
|
||||||
|
import "cube.kcl" as block // Introduces a new variable called `block`.
|
||||||
|
import "cube/main.kcl" // Introduces a new variable called `cube`.
|
||||||
|
import "cube/main.kcl" as block // Introduces a new variable called `block`.
|
||||||
|
```
|
||||||
|
|
||||||
|
If the filename includes hyphens (`-`) or starts with an underscore (`_`), then you must specify a
|
||||||
|
variable name.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Multiple instances of the same import
|
## Multiple instances of the same import
|
||||||
@ -241,7 +254,7 @@ If you want to have multiple instances of the same object, you can use the
|
|||||||
[`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory.
|
[`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory.
|
||||||
|
|
||||||
```norun
|
```norun
|
||||||
import cube from "tests/inputs/cube.kcl"
|
import cube from "cube.kcl"
|
||||||
|
|
||||||
cube
|
cube
|
||||||
|> translate(x=10)
|
|> translate(x=10)
|
||||||
@ -257,7 +270,7 @@ separate objects in memory, and can be manipulated independently.
|
|||||||
Here is an example with a file from another CAD system:
|
Here is an example with a file from another CAD system:
|
||||||
|
|
||||||
```kcl
|
```kcl
|
||||||
import "tests/inputs/cube.step" as cube
|
import "tests/inputs/cube.step"
|
||||||
|
|
||||||
cube
|
cube
|
||||||
|> translate(x=10)
|
|> translate(x=10)
|
||||||
|
@ -13,6 +13,7 @@ arc(
|
|||||||
angleStart?: number,
|
angleStart?: number,
|
||||||
angleEnd?: number,
|
angleEnd?: number,
|
||||||
radius?: number,
|
radius?: number,
|
||||||
|
diameter?: number,
|
||||||
interiorAbsolute?: Point2d,
|
interiorAbsolute?: Point2d,
|
||||||
endAbsolute?: Point2d,
|
endAbsolute?: Point2d,
|
||||||
tag?: TagDeclarator,
|
tag?: TagDeclarator,
|
||||||
@ -30,7 +31,8 @@ Unless this makes a lot of sense and feels like what you're looking for to const
|
|||||||
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | Which sketch should this path be added to? | Yes |
|
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | Which sketch should this path be added to? | Yes |
|
||||||
| `angleStart` | [`number`](/docs/kcl-std/types/std-types-number) | Where along the circle should this arc start? | No |
|
| `angleStart` | [`number`](/docs/kcl-std/types/std-types-number) | Where along the circle should this arc start? | No |
|
||||||
| `angleEnd` | [`number`](/docs/kcl-std/types/std-types-number) | Where along the circle should this arc end? | No |
|
| `angleEnd` | [`number`](/docs/kcl-std/types/std-types-number) | Where along the circle should this arc end? | No |
|
||||||
| `radius` | [`number`](/docs/kcl-std/types/std-types-number) | How large should the circle be? | No |
|
| `radius` | [`number`](/docs/kcl-std/types/std-types-number) | How large should the circle be? Incompatible with `diameter`. | No |
|
||||||
|
| `diameter` | [`number`](/docs/kcl-std/types/std-types-number) | How large should the circle be? Incompatible with `radius`. | No |
|
||||||
| `interiorAbsolute` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | Any point between the arc's start and end? Requires `endAbsolute`. Incompatible with `angleStart` or `angleEnd` | No |
|
| `interiorAbsolute` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | Any point between the arc's start and end? Requires `endAbsolute`. Incompatible with `angleStart` or `angleEnd` | No |
|
||||||
| `endAbsolute` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | Where should this arc end? Requires `interiorAbsolute`. Incompatible with `angleStart` or `angleEnd` | No |
|
| `endAbsolute` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | Where should this arc end? Requires `interiorAbsolute`. Incompatible with `angleStart` or `angleEnd` | No |
|
||||||
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | Create a new tag which refers to this line | No |
|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | Create a new tag which refers to this line | No |
|
||||||
|
127
docs/kcl-std/functions/std-appearance-hexString.md
Normal file
64
docs/kcl-std/functions/std-appearance-rgb.md
Normal file
@ -12,7 +12,7 @@ reduce(
|
|||||||
@array: [any],
|
@array: [any],
|
||||||
initial: any,
|
initial: any,
|
||||||
f: fn(any, accum: any): any,
|
f: fn(any, accum: any): any,
|
||||||
): [any]
|
): any
|
||||||
```
|
```
|
||||||
|
|
||||||
Take a starting value. Then, for each element of an array, calculate the next value,
|
Take a starting value. Then, for each element of an array, calculate the next value,
|
||||||
@ -28,7 +28,7 @@ using the previous value and the element.
|
|||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
[`[any]`](/docs/kcl-std/types/std-types-any)
|
[`any`](/docs/kcl-std/types/std-types-any)
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
@ -11,7 +11,7 @@ Compute the length of the given leg.
|
|||||||
legLen(
|
legLen(
|
||||||
hypotenuse: number(Length),
|
hypotenuse: number(Length),
|
||||||
leg: number(Length),
|
leg: number(Length),
|
||||||
): number(deg)
|
): number(Length)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ legLen(
|
|||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
|
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
@ -11,7 +11,8 @@ layout: manual
|
|||||||
circle(
|
circle(
|
||||||
@sketch_or_surface: Sketch | Plane | Face,
|
@sketch_or_surface: Sketch | Plane | Face,
|
||||||
center: Point2d,
|
center: Point2d,
|
||||||
radius: number(Length),
|
radius?: number(Length),
|
||||||
|
diameter?: number(Length),
|
||||||
tag?: tag,
|
tag?: tag,
|
||||||
): Sketch
|
): Sketch
|
||||||
```
|
```
|
||||||
@ -25,7 +26,8 @@ the provided (x, y) origin point.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `sketch_or_surface` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Plane`](/docs/kcl-std/types/std-types-Plane) or [`Face`](/docs/kcl-std/types/std-types-Face) | Sketch to extend, or plane or surface to sketch on. | Yes |
|
| `sketch_or_surface` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Plane`](/docs/kcl-std/types/std-types-Plane) or [`Face`](/docs/kcl-std/types/std-types-Face) | Sketch to extend, or plane or surface to sketch on. | Yes |
|
||||||
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center of the circle. | Yes |
|
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center of the circle. | Yes |
|
||||||
| `radius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The radius of the circle. | Yes |
|
| `radius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The radius of the circle. Incompatible with `diameter`. | No |
|
||||||
|
| `diameter` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The diameter of the circle. Incompatible with `radius`. | No |
|
||||||
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | Create a new tag which refers to this circle. | No |
|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | Create a new tag which refers to this circle. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
@ -51,7 +53,7 @@ exampleSketch = startSketchOn(XZ)
|
|||||||
|> line(end = [0, 30])
|
|> line(end = [0, 30])
|
||||||
|> line(end = [-30, 0])
|
|> line(end = [-30, 0])
|
||||||
|> close()
|
|> close()
|
||||||
|> subtract2d(tool = circle(center = [0, 15], radius = 5))
|
|> subtract2d(tool = circle(center = [0, 15], diameter = 10))
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 5)
|
example = extrude(exampleSketch, length = 5)
|
||||||
```
|
```
|
||||||
|
@ -8,7 +8,7 @@ layout: manual
|
|||||||
Get the shared edge between two faces.
|
Get the shared edge between two faces.
|
||||||
|
|
||||||
```kcl
|
```kcl
|
||||||
getCommonEdge(faces: [TagIdentifier]): Uuid
|
getCommonEdge(faces: [tag; 2]): Edge
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,11 +17,11 @@ getCommonEdge(faces: [TagIdentifier]): Uuid
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `faces` | [`[TagIdentifier]`](/docs/kcl-lang/types#TagIdentifier) | The tags of the faces you want to find the common edge between | Yes |
|
| `faces` | `[tag; 2]` | The tags of the faces you want to find the common edge between. | Yes |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
`Uuid`
|
[`Edge`](/docs/kcl-std/types/std-types-Edge) - An edge of a solid.
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
@ -29,17 +29,16 @@ getCommonEdge(faces: [TagIdentifier]): Uuid
|
|||||||
```kcl
|
```kcl
|
||||||
// Get an edge shared between two faces, created after a chamfer.
|
// Get an edge shared between two faces, created after a chamfer.
|
||||||
|
|
||||||
|
|
||||||
scale = 20
|
scale = 20
|
||||||
part001 = startSketchOn(XY)
|
part001 = startSketchOn(XY)
|
||||||
|> startProfile(at = [0, 0])
|
|> startProfile(at = [0, 0])
|
||||||
|> line(end = [0, scale])
|
|> line(end = [0, scale])
|
||||||
|> line(end = [scale, 0])
|
|> line(end = [scale, 0])
|
||||||
|> line(end = [0, -scale])
|
|> line(end = [0, -scale])
|
||||||
|> close(tag = $line0)
|
|> close(tag = $line0)
|
||||||
|> extrude(length = 20, tagEnd = $end0)
|
|> extrude(length = 20, tagEnd = $end0)
|
||||||
// We tag the chamfer to reference it later.
|
// We tag the chamfer to reference it later.
|
||||||
|> chamfer(length = 10, tags = [getOppositeEdge(line0)], tag = $chamfer0)
|
|> chamfer(length = 10, tags = [getOppositeEdge(line0)], tag = $chamfer0)
|
||||||
|
|
||||||
// Get the shared edge between the chamfer and the extrusion.
|
// Get the shared edge between the chamfer and the extrusion.
|
||||||
commonEdge = getCommonEdge(faces = [chamfer0, end0])
|
commonEdge = getCommonEdge(faces = [chamfer0, end0])
|
@ -1,19 +1,19 @@
|
|||||||
---
|
---
|
||||||
title: "patternTransform2d"
|
title: "patternTransform2d"
|
||||||
subtitle: "Function in std::sketch"
|
subtitle: "Function in std::sketch"
|
||||||
excerpt: "Just like patternTransform, but works on 2D sketches not 3D solids."
|
excerpt: "Just like `patternTransform`, but works on 2D sketches not 3D solids."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Just like patternTransform, but works on 2D sketches not 3D solids.
|
Just like `patternTransform`, but works on 2D sketches not 3D solids.
|
||||||
|
|
||||||
```kcl
|
```kcl
|
||||||
patternTransform2d(
|
patternTransform2d(
|
||||||
@sketches: [Sketch],
|
@sketches: [Sketch; 1+],
|
||||||
instances: number,
|
instances: number(_),
|
||||||
transform: FunctionSource,
|
transform: fn(number(_)): { },
|
||||||
useOriginal?: bool,
|
useOriginal?: boolean,
|
||||||
): [Sketch]
|
): [Sketch; 1+]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -22,14 +22,14 @@ patternTransform2d(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `sketches` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) to duplicate | Yes |
|
| `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) to duplicate. | Yes |
|
||||||
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
| `instances` | [`number(_)`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||||
| `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
|
| `transform` | [`fn(number(_)): { }`](/docs/kcl-std/types/std-types-fn) | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
|
||||||
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
| `useOriginal` | `boolean` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
[`[Sketch]`](/docs/kcl-std/types/std-types-Sketch)
|
[`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch)
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
@ -1,31 +1,36 @@
|
|||||||
---
|
---
|
||||||
title: "intersect"
|
title: "intersect"
|
||||||
subtitle: "Function in std::solid"
|
subtitle: "Function in std::solid"
|
||||||
excerpt: "Intersect returns the shared volume between multiple solids, preserving only overlapping regions."
|
excerpt: ""
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Intersect returns the shared volume between multiple solids, preserving only overlapping regions.
|
|
||||||
|
|
||||||
```kcl
|
```kcl
|
||||||
intersect(
|
intersect(
|
||||||
@solids: [Solid],
|
@solids: [Solid; 2+],
|
||||||
tolerance?: number,
|
tolerance?: number(Length),
|
||||||
): [Solid]
|
): [Solid; 1+]
|
||||||
```
|
```
|
||||||
|
|
||||||
Intersect computes the geometric intersection of multiple solid bodies, returning a new solid representing the volume that is common to all input solids. This operation is useful for determining shared material regions, verifying fit, and analyzing overlapping geometries in assemblies.
|
Intersect returns the shared volume between multiple solids, preserving only
|
||||||
|
overlapping regions.
|
||||||
|
Intersect computes the geometric intersection of multiple solid bodies,
|
||||||
|
returning a new solid representing the volume that is common to all input
|
||||||
|
solids. This operation is useful for determining shared material regions,
|
||||||
|
verifying fit, and analyzing overlapping geometries in assemblies.
|
||||||
|
|
||||||
### Arguments
|
### Arguments
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) | The solids to intersect. | Yes |
|
| `solids` | `[Solid; 2+]` | The solids to intersect. | Yes |
|
||||||
| `tolerance` | [`number`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the intersection operation. | No |
|
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the intersection operation. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
[`[Solid]`](/docs/kcl-std/types/std-types-Solid)
|
[`[Solid; 1+]`](/docs/kcl-std/types/std-types-Solid)
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
@ -33,20 +38,19 @@ Intersect computes the geometric intersection of multiple solid bodies, returnin
|
|||||||
```kcl
|
```kcl
|
||||||
// Intersect two cubes using the stdlib functions.
|
// Intersect two cubes using the stdlib functions.
|
||||||
|
|
||||||
|
|
||||||
fn cube(center, size) {
|
fn cube(center, size) {
|
||||||
return startSketchOn(XY)
|
return startSketchOn(XY)
|
||||||
|> startProfile(at = [center[0] - size, center[1] - size])
|
|> startProfile(at = [center[0] - size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||||
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 10)
|
|> extrude(length = 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
part001 = cube(center = [0, 0], size = 10)
|
part001 = cube(center = [0, 0], size = 10)
|
||||||
part002 = cube(center = [7, 3], size = 5)
|
part002 = cube(center = [7, 3], size = 5)
|
||||||
|> translate(z = 1)
|
|> translate(z = 1)
|
||||||
|
|
||||||
intersectedPart = intersect([part001, part002])
|
intersectedPart = intersect([part001, part002])
|
||||||
```
|
```
|
||||||
@ -58,20 +62,19 @@ intersectedPart = intersect([part001, part002])
|
|||||||
// NOTE: This will not work when using codemods through the UI.
|
// NOTE: This will not work when using codemods through the UI.
|
||||||
// Codemods will generate the stdlib function call instead.
|
// Codemods will generate the stdlib function call instead.
|
||||||
|
|
||||||
|
|
||||||
fn cube(center, size) {
|
fn cube(center, size) {
|
||||||
return startSketchOn(XY)
|
return startSketchOn(XY)
|
||||||
|> startProfile(at = [center[0] - size, center[1] - size])
|
|> startProfile(at = [center[0] - size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||||
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 10)
|
|> extrude(length = 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
part001 = cube(center = [0, 0], size = 10)
|
part001 = cube(center = [0, 0], size = 10)
|
||||||
part002 = cube(center = [7, 3], size = 5)
|
part002 = cube(center = [7, 3], size = 5)
|
||||||
|> translate(z = 1)
|
|> translate(z = 1)
|
||||||
|
|
||||||
// This is the equivalent of: intersect([part001, part002])
|
// This is the equivalent of: intersect([part001, part002])
|
||||||
intersectedPart = part001 & part002
|
intersectedPart = part001 & part002
|
@ -9,9 +9,9 @@ Union two or more solids into a single solid.
|
|||||||
|
|
||||||
```kcl
|
```kcl
|
||||||
union(
|
union(
|
||||||
@solids: [Solid],
|
@solids: [Solid; 2+],
|
||||||
tolerance?: number,
|
tolerance?: number(Length),
|
||||||
): [Solid]
|
): [Solid; 1+]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -20,12 +20,12 @@ union(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) | The solids to union. | Yes |
|
| `solids` | `[Solid; 2+]` | The solids to union. | Yes |
|
||||||
| `tolerance` | [`number`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the union operation. | No |
|
| `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the union operation. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
[`[Solid]`](/docs/kcl-std/types/std-types-Solid)
|
[`[Solid; 1+]`](/docs/kcl-std/types/std-types-Solid)
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
@ -33,20 +33,19 @@ union(
|
|||||||
```kcl
|
```kcl
|
||||||
// Union two cubes using the stdlib functions.
|
// Union two cubes using the stdlib functions.
|
||||||
|
|
||||||
|
|
||||||
fn cube(center, size) {
|
fn cube(center, size) {
|
||||||
return startSketchOn(XY)
|
return startSketchOn(XY)
|
||||||
|> startProfile(at = [center[0] - size, center[1] - size])
|
|> startProfile(at = [center[0] - size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||||
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 10)
|
|> extrude(length = 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
part001 = cube(center = [0, 0], size = 10)
|
part001 = cube(center = [0, 0], size = 10)
|
||||||
part002 = cube(center = [7, 3], size = 5)
|
part002 = cube(center = [7, 3], size = 5)
|
||||||
|> translate(z = 1)
|
|> translate(z = 1)
|
||||||
|
|
||||||
unionedPart = union([part001, part002])
|
unionedPart = union([part001, part002])
|
||||||
```
|
```
|
||||||
@ -58,20 +57,19 @@ unionedPart = union([part001, part002])
|
|||||||
// NOTE: This will not work when using codemods through the UI.
|
// NOTE: This will not work when using codemods through the UI.
|
||||||
// Codemods will generate the stdlib function call instead.
|
// Codemods will generate the stdlib function call instead.
|
||||||
|
|
||||||
|
|
||||||
fn cube(center, size) {
|
fn cube(center, size) {
|
||||||
return startSketchOn(XY)
|
return startSketchOn(XY)
|
||||||
|> startProfile(at = [center[0] - size, center[1] - size])
|
|> startProfile(at = [center[0] - size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||||
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 10)
|
|> extrude(length = 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
part001 = cube(center = [0, 0], size = 10)
|
part001 = cube(center = [0, 0], size = 10)
|
||||||
part002 = cube(center = [7, 3], size = 5)
|
part002 = cube(center = [7, 3], size = 5)
|
||||||
|> translate(z = 1)
|
|> translate(z = 1)
|
||||||
|
|
||||||
// This is the equivalent of: union([part001, part002])
|
// This is the equivalent of: union([part001, part002])
|
||||||
unionedPart = part001 + part002
|
unionedPart = part001 + part002
|
||||||
@ -84,23 +82,22 @@ unionedPart = part001 + part002
|
|||||||
// NOTE: This will not work when using codemods through the UI.
|
// NOTE: This will not work when using codemods through the UI.
|
||||||
// Codemods will generate the stdlib function call instead.
|
// Codemods will generate the stdlib function call instead.
|
||||||
|
|
||||||
|
|
||||||
fn cube(center, size) {
|
fn cube(center, size) {
|
||||||
return startSketchOn(XY)
|
return startSketchOn(XY)
|
||||||
|> startProfile(at = [center[0] - size, center[1] - size])
|
|> startProfile(at = [center[0] - size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
|> line(endAbsolute = [center[0] + size, center[1] - size])
|
||||||
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
|> line(endAbsolute = [center[0] + size, center[1] + size])
|
||||||
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
|> line(endAbsolute = [center[0] - size, center[1] + size])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 10)
|
|> extrude(length = 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
part001 = cube(center = [0, 0], size = 10)
|
part001 = cube(center = [0, 0], size = 10)
|
||||||
part002 = cube(center = [7, 3], size = 5)
|
part002 = cube(center = [7, 3], size = 5)
|
||||||
|> translate(z = 1)
|
|> translate(z = 1)
|
||||||
|
|
||||||
// This is the equivalent of: union([part001, part002])
|
// This is the equivalent of: union([part001, part002])
|
||||||
// Programmers will understand `|` as a union operation, but mechanical engineers
|
// Programmers will understand `|` as a union operation, but mechanical engineers
|
||||||
// will understand `+`, we made both work.
|
// will understand `+`, we made both work.
|
||||||
unionedPart = part001 | part002
|
unionedPart = part001 | part002
|
||||||
```
|
```
|
@ -14,8 +14,6 @@ mirror2d(
|
|||||||
): Sketch
|
): Sketch
|
||||||
```
|
```
|
||||||
|
|
||||||
Only works on unclosed sketches for now.
|
|
||||||
|
|
||||||
Mirror occurs around a local sketch axis rather than a global axis.
|
Mirror occurs around a local sketch axis rather than a global axis.
|
||||||
|
|
||||||
### Arguments
|
### Arguments
|
||||||
|
@ -9,13 +9,14 @@ layout: manual
|
|||||||
### Functions
|
### Functions
|
||||||
|
|
||||||
* [**std**](/docs/kcl-std/modules/std)
|
* [**std**](/docs/kcl-std/modules/std)
|
||||||
* [`appearance`](/docs/kcl-std/appearance)
|
|
||||||
* [`assert`](/docs/kcl-std/assert)
|
* [`assert`](/docs/kcl-std/assert)
|
||||||
* [`assertIs`](/docs/kcl-std/assertIs)
|
* [`assertIs`](/docs/kcl-std/assertIs)
|
||||||
* [`clone`](/docs/kcl-std/functions/std-clone)
|
* [`clone`](/docs/kcl-std/functions/std-clone)
|
||||||
* [`helix`](/docs/kcl-std/functions/std-helix)
|
* [`helix`](/docs/kcl-std/functions/std-helix)
|
||||||
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
|
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
|
||||||
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d)
|
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d)
|
||||||
|
* [**std::appearance**](/docs/kcl-std/modules/std-appearance)
|
||||||
|
* [`appearance::hexString`](/docs/kcl-std/functions/std-appearance-hexString)
|
||||||
* [**std::array**](/docs/kcl-std/modules/std-array)
|
* [**std::array**](/docs/kcl-std/modules/std-array)
|
||||||
* [`map`](/docs/kcl-std/functions/std-array-map)
|
* [`map`](/docs/kcl-std/functions/std-array-map)
|
||||||
* [`pop`](/docs/kcl-std/functions/std-array-pop)
|
* [`pop`](/docs/kcl-std/functions/std-array-pop)
|
||||||
@ -55,17 +56,17 @@ layout: manual
|
|||||||
* [`circleThreePoint`](/docs/kcl-std/circleThreePoint)
|
* [`circleThreePoint`](/docs/kcl-std/circleThreePoint)
|
||||||
* [`close`](/docs/kcl-std/close)
|
* [`close`](/docs/kcl-std/close)
|
||||||
* [`extrude`](/docs/kcl-std/extrude)
|
* [`extrude`](/docs/kcl-std/extrude)
|
||||||
* [`getCommonEdge`](/docs/kcl-std/getCommonEdge)
|
* [`getCommonEdge`](/docs/kcl-std/functions/std-sketch-getCommonEdge)
|
||||||
* [`getNextAdjacentEdge`](/docs/kcl-std/getNextAdjacentEdge)
|
* [`getNextAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getNextAdjacentEdge)
|
||||||
* [`getOppositeEdge`](/docs/kcl-std/getOppositeEdge)
|
* [`getOppositeEdge`](/docs/kcl-std/functions/std-sketch-getOppositeEdge)
|
||||||
* [`getPreviousAdjacentEdge`](/docs/kcl-std/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getPreviousAdjacentEdge)
|
||||||
* [`involuteCircular`](/docs/kcl-std/involuteCircular)
|
* [`involuteCircular`](/docs/kcl-std/involuteCircular)
|
||||||
* [`lastSegX`](/docs/kcl-std/lastSegX)
|
* [`lastSegX`](/docs/kcl-std/lastSegX)
|
||||||
* [`lastSegY`](/docs/kcl-std/lastSegY)
|
* [`lastSegY`](/docs/kcl-std/lastSegY)
|
||||||
* [`line`](/docs/kcl-std/line)
|
* [`line`](/docs/kcl-std/line)
|
||||||
* [`loft`](/docs/kcl-std/loft)
|
* [`loft`](/docs/kcl-std/loft)
|
||||||
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
|
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
|
||||||
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
|
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
|
||||||
* [`polygon`](/docs/kcl-std/polygon)
|
* [`polygon`](/docs/kcl-std/polygon)
|
||||||
* [`profileStart`](/docs/kcl-std/profileStart)
|
* [`profileStart`](/docs/kcl-std/profileStart)
|
||||||
* [`profileStartX`](/docs/kcl-std/profileStartX)
|
* [`profileStartX`](/docs/kcl-std/profileStartX)
|
||||||
@ -88,21 +89,22 @@ layout: manual
|
|||||||
* [`xLine`](/docs/kcl-std/xLine)
|
* [`xLine`](/docs/kcl-std/xLine)
|
||||||
* [`yLine`](/docs/kcl-std/yLine)
|
* [`yLine`](/docs/kcl-std/yLine)
|
||||||
* [**std::solid**](/docs/kcl-std/modules/std-solid)
|
* [**std::solid**](/docs/kcl-std/modules/std-solid)
|
||||||
|
* [`appearance`](/docs/kcl-std/functions/std-solid-appearance)
|
||||||
* [`chamfer`](/docs/kcl-std/functions/std-solid-chamfer)
|
* [`chamfer`](/docs/kcl-std/functions/std-solid-chamfer)
|
||||||
* [`fillet`](/docs/kcl-std/functions/std-solid-fillet)
|
* [`fillet`](/docs/kcl-std/functions/std-solid-fillet)
|
||||||
* [`hollow`](/docs/kcl-std/functions/std-solid-hollow)
|
* [`hollow`](/docs/kcl-std/functions/std-solid-hollow)
|
||||||
* [`intersect`](/docs/kcl-std/intersect)
|
* [`intersect`](/docs/kcl-std/functions/std-solid-intersect)
|
||||||
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
|
* [`patternCircular3d`](/docs/kcl-std/functions/std-solid-patternCircular3d)
|
||||||
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
|
* [`patternLinear3d`](/docs/kcl-std/functions/std-solid-patternLinear3d)
|
||||||
* [`patternTransform`](/docs/kcl-std/patternTransform)
|
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform)
|
||||||
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
|
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
|
||||||
* [`subtract`](/docs/kcl-std/subtract)
|
* [`subtract`](/docs/kcl-std/functions/std-solid-subtract)
|
||||||
* [`union`](/docs/kcl-std/union)
|
* [`union`](/docs/kcl-std/functions/std-solid-union)
|
||||||
* [**std::transform**](/docs/kcl-std/modules/std-transform)
|
* [**std::transform**](/docs/kcl-std/modules/std-transform)
|
||||||
* [`mirror2d`](/docs/kcl-std/functions/std-transform-mirror2d)
|
* [`mirror2d`](/docs/kcl-std/functions/std-transform-mirror2d)
|
||||||
* [`rotate`](/docs/kcl-std/rotate)
|
* [`rotate`](/docs/kcl-std/functions/std-transform-rotate)
|
||||||
* [`scale`](/docs/kcl-std/scale)
|
* [`scale`](/docs/kcl-std/functions/std-transform-scale)
|
||||||
* [`translate`](/docs/kcl-std/translate)
|
* [`translate`](/docs/kcl-std/functions/std-transform-translate)
|
||||||
* [**std::units**](/docs/kcl-std/modules/std-units)
|
* [**std::units**](/docs/kcl-std/modules/std-units)
|
||||||
* [`units::toCentimeters`](/docs/kcl-std/functions/std-units-toCentimeters)
|
* [`units::toCentimeters`](/docs/kcl-std/functions/std-units-toCentimeters)
|
||||||
* [`units::toDegrees`](/docs/kcl-std/functions/std-units-toDegrees)
|
* [`units::toDegrees`](/docs/kcl-std/functions/std-units-toDegrees)
|
||||||
|
16
docs/kcl-std/modules/std-appearance.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: "appearance"
|
||||||
|
subtitle: "Module in std"
|
||||||
|
excerpt: ""
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Functions and constants
|
||||||
|
|
||||||
|
* [`appearance::hexString`](/docs/kcl-std/functions/std-appearance-hexString)
|
||||||
|
|
@ -20,17 +20,17 @@ This module contains functions for creating and manipulating sketches, and makin
|
|||||||
* [`circleThreePoint`](/docs/kcl-std/circleThreePoint)
|
* [`circleThreePoint`](/docs/kcl-std/circleThreePoint)
|
||||||
* [`close`](/docs/kcl-std/close)
|
* [`close`](/docs/kcl-std/close)
|
||||||
* [`extrude`](/docs/kcl-std/extrude)
|
* [`extrude`](/docs/kcl-std/extrude)
|
||||||
* [`getCommonEdge`](/docs/kcl-std/getCommonEdge)
|
* [`getCommonEdge`](/docs/kcl-std/functions/std-sketch-getCommonEdge)
|
||||||
* [`getNextAdjacentEdge`](/docs/kcl-std/getNextAdjacentEdge)
|
* [`getNextAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getNextAdjacentEdge)
|
||||||
* [`getOppositeEdge`](/docs/kcl-std/getOppositeEdge)
|
* [`getOppositeEdge`](/docs/kcl-std/functions/std-sketch-getOppositeEdge)
|
||||||
* [`getPreviousAdjacentEdge`](/docs/kcl-std/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getPreviousAdjacentEdge)
|
||||||
* [`involuteCircular`](/docs/kcl-std/involuteCircular)
|
* [`involuteCircular`](/docs/kcl-std/involuteCircular)
|
||||||
* [`lastSegX`](/docs/kcl-std/lastSegX)
|
* [`lastSegX`](/docs/kcl-std/lastSegX)
|
||||||
* [`lastSegY`](/docs/kcl-std/lastSegY)
|
* [`lastSegY`](/docs/kcl-std/lastSegY)
|
||||||
* [`line`](/docs/kcl-std/line)
|
* [`line`](/docs/kcl-std/line)
|
||||||
* [`loft`](/docs/kcl-std/loft)
|
* [`loft`](/docs/kcl-std/loft)
|
||||||
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
|
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
|
||||||
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
|
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
|
||||||
* [`polygon`](/docs/kcl-std/polygon)
|
* [`polygon`](/docs/kcl-std/polygon)
|
||||||
* [`profileStart`](/docs/kcl-std/profileStart)
|
* [`profileStart`](/docs/kcl-std/profileStart)
|
||||||
* [`profileStartX`](/docs/kcl-std/profileStartX)
|
* [`profileStartX`](/docs/kcl-std/profileStartX)
|
||||||
|
@ -12,14 +12,15 @@ This module contains functions for modifying solids, e.g., by adding a fillet or
|
|||||||
|
|
||||||
## Functions and constants
|
## Functions and constants
|
||||||
|
|
||||||
|
* [`appearance`](/docs/kcl-std/functions/std-solid-appearance)
|
||||||
* [`chamfer`](/docs/kcl-std/functions/std-solid-chamfer)
|
* [`chamfer`](/docs/kcl-std/functions/std-solid-chamfer)
|
||||||
* [`fillet`](/docs/kcl-std/functions/std-solid-fillet)
|
* [`fillet`](/docs/kcl-std/functions/std-solid-fillet)
|
||||||
* [`hollow`](/docs/kcl-std/functions/std-solid-hollow)
|
* [`hollow`](/docs/kcl-std/functions/std-solid-hollow)
|
||||||
* [`intersect`](/docs/kcl-std/intersect)
|
* [`intersect`](/docs/kcl-std/functions/std-solid-intersect)
|
||||||
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
|
* [`patternCircular3d`](/docs/kcl-std/functions/std-solid-patternCircular3d)
|
||||||
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
|
* [`patternLinear3d`](/docs/kcl-std/functions/std-solid-patternLinear3d)
|
||||||
* [`patternTransform`](/docs/kcl-std/patternTransform)
|
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform)
|
||||||
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
|
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
|
||||||
* [`subtract`](/docs/kcl-std/subtract)
|
* [`subtract`](/docs/kcl-std/functions/std-solid-subtract)
|
||||||
* [`union`](/docs/kcl-std/union)
|
* [`union`](/docs/kcl-std/functions/std-solid-union)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ This module contains functions for transforming sketches and solids.
|
|||||||
## Functions and constants
|
## Functions and constants
|
||||||
|
|
||||||
* [`mirror2d`](/docs/kcl-std/functions/std-transform-mirror2d)
|
* [`mirror2d`](/docs/kcl-std/functions/std-transform-mirror2d)
|
||||||
* [`rotate`](/docs/kcl-std/rotate)
|
* [`rotate`](/docs/kcl-std/functions/std-transform-rotate)
|
||||||
* [`scale`](/docs/kcl-std/scale)
|
* [`scale`](/docs/kcl-std/functions/std-transform-scale)
|
||||||
* [`translate`](/docs/kcl-std/translate)
|
* [`translate`](/docs/kcl-std/functions/std-transform-translate)
|
||||||
|
|
||||||
|
@ -11,10 +11,11 @@ Contains frequently used constants, functions for interacting with the KittyCAD
|
|||||||
|
|
||||||
The standard library is organised into modules (listed below), but most things are always available in KCL programs.
|
The standard library is organised into modules (listed below), but most things are always available in KCL programs.
|
||||||
|
|
||||||
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide]().
|
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide](https://zoo.dev/docs/kcl-book/intro.html).
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
|
* [`appearance::appearance`](/docs/kcl-std/modules/std-appearance)
|
||||||
* [`array`](/docs/kcl-std/modules/std-array)
|
* [`array`](/docs/kcl-std/modules/std-array)
|
||||||
* [`math`](/docs/kcl-std/modules/std-math)
|
* [`math`](/docs/kcl-std/modules/std-math)
|
||||||
* [`sketch`](/docs/kcl-std/modules/std-sketch)
|
* [`sketch`](/docs/kcl-std/modules/std-sketch)
|
||||||
@ -35,7 +36,6 @@ You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL gui
|
|||||||
* [`Y`](/docs/kcl-std/consts/std-Y)
|
* [`Y`](/docs/kcl-std/consts/std-Y)
|
||||||
* [`YZ`](/docs/kcl-std/consts/std-YZ)
|
* [`YZ`](/docs/kcl-std/consts/std-YZ)
|
||||||
* [`Z`](/docs/kcl-std/consts/std-Z)
|
* [`Z`](/docs/kcl-std/consts/std-Z)
|
||||||
* [`appearance`](/docs/kcl-std/appearance)
|
|
||||||
* [`assert`](/docs/kcl-std/assert)
|
* [`assert`](/docs/kcl-std/assert)
|
||||||
* [`assertIs`](/docs/kcl-std/assertIs)
|
* [`assertIs`](/docs/kcl-std/assertIs)
|
||||||
* [`clone`](/docs/kcl-std/functions/std-clone)
|
* [`clone`](/docs/kcl-std/functions/std-clone)
|
||||||
|
@ -12,8 +12,8 @@ patternCircular2d(
|
|||||||
@sketchSet: [Sketch],
|
@sketchSet: [Sketch],
|
||||||
instances: number,
|
instances: number,
|
||||||
center: Point2d,
|
center: Point2d,
|
||||||
arcDegrees: number,
|
arcDegrees?: number,
|
||||||
rotateDuplicates: bool,
|
rotateDuplicates?: bool,
|
||||||
useOriginal?: bool,
|
useOriginal?: bool,
|
||||||
): [Sketch]
|
): [Sketch]
|
||||||
```
|
```
|
||||||
@ -27,8 +27,8 @@ patternCircular2d(
|
|||||||
| `sketchSet` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch(es) to pattern | Yes |
|
| `sketchSet` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch(es) to pattern | Yes |
|
||||||
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||||
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center about which to make the pattern. This is a 2D vector. | Yes |
|
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center about which to make the pattern. This is a 2D vector. | Yes |
|
||||||
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360. | No |
|
||||||
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. | Yes |
|
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. Defaults to true. | No |
|
||||||
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
103962
docs/kcl-std/std.json
@ -13,6 +13,7 @@ tangentialArc(
|
|||||||
endAbsolute?: Point2d,
|
endAbsolute?: Point2d,
|
||||||
end?: Point2d,
|
end?: Point2d,
|
||||||
radius?: number,
|
radius?: number,
|
||||||
|
diameter?: number,
|
||||||
angle?: number,
|
angle?: number,
|
||||||
tag?: TagDeclarator,
|
tag?: TagDeclarator,
|
||||||
): Sketch
|
): Sketch
|
||||||
@ -27,7 +28,8 @@ When using radius and angle, draw a curved line segment along part of an imagina
|
|||||||
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | Which sketch should this path be added to? | Yes |
|
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | Which sketch should this path be added to? | Yes |
|
||||||
| `endAbsolute` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | Which absolute point should this arc go to? Incompatible with `end`, `radius`, and `offset`. | No |
|
| `endAbsolute` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | Which absolute point should this arc go to? Incompatible with `end`, `radius`, and `offset`. | No |
|
||||||
| `end` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | How far away (along the X and Y axes) should this arc go? Incompatible with `endAbsolute`, `radius`, and `offset`. | No |
|
| `end` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | How far away (along the X and Y axes) should this arc go? Incompatible with `endAbsolute`, `radius`, and `offset`. | No |
|
||||||
| `radius` | [`number`](/docs/kcl-std/types/std-types-number) | Radius of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute`. | No |
|
| `radius` | [`number`](/docs/kcl-std/types/std-types-number) | Radius of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute` and `diameter`. | No |
|
||||||
|
| `diameter` | [`number`](/docs/kcl-std/types/std-types-number) | Diameter of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute` and `radius`. | No |
|
||||||
| `angle` | [`number`](/docs/kcl-std/types/std-types-number) | Offset of the arc in degrees. `radius` must be given. Incompatible with `end` and `endAbsolute`. | No |
|
| `angle` | [`number`](/docs/kcl-std/types/std-types-number) | Offset of the arc in degrees. `radius` must be given. Incompatible with `end` and `endAbsolute`. | No |
|
||||||
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | Create a new tag which refers to this arc | No |
|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | Create a new tag which refers to this arc | No |
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import * as fsp from 'fs/promises'
|
|||||||
test.describe('Electron app header tests', () => {
|
test.describe('Electron app header tests', () => {
|
||||||
test(
|
test(
|
||||||
'Open Command Palette button has correct shortcut',
|
'Open Command Palette button has correct shortcut',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ test.describe('Electron app header tests', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'User settings has correct shortcut',
|
'User settings has correct shortcut',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, toolbar }, testInfo) => {
|
async ({ page, toolbar }, testInfo) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
|||||||
test.describe('Authentication tests', () => {
|
test.describe('Authentication tests', () => {
|
||||||
test(
|
test(
|
||||||
`The user can sign out and back in`,
|
`The user can sign out and back in`,
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@desktop'] },
|
||||||
async ({ page, homePage, signInPage, toolbar, tronApp }) => {
|
async ({ page, homePage, signInPage, toolbar, tronApp }) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
|
@ -78,11 +78,10 @@ extrude001 = extrude(sketch001, length = 5)`
|
|||||||
|
|
||||||
// Delete a character to break the KCL
|
// Delete a character to break the KCL
|
||||||
await editor.openPane()
|
await editor.openPane()
|
||||||
await editor.scrollToText('bracketLeg1Sketch, length = thickness)')
|
await editor.scrollToText('extrude(%, length = width)')
|
||||||
await page
|
await page.getByText('extrude(%, length = width)').click()
|
||||||
.getByText('extrude(bracketLeg1Sketch, length = thickness)')
|
|
||||||
.click()
|
await page.keyboard.press(')')
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
|
|
||||||
// Ensure that a badge appears on the button
|
// Ensure that a badge appears on the button
|
||||||
await expect(codePaneButtonHolder).toContainText('notification')
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
@ -99,16 +98,11 @@ extrude001 = extrude(sketch001, length = 5)`
|
|||||||
|
|
||||||
await page.waitForTimeout(500)
|
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
|
// Open the code pane
|
||||||
await editor.openPane()
|
await editor.openPane()
|
||||||
|
|
||||||
// Go to our problematic code again (missing closing paren!)
|
// Go to our problematic code again
|
||||||
await editor.scrollToText('extrude(bracketLeg1Sketch, length = thickness')
|
await editor.scrollToText('extrude(%, length = w')
|
||||||
|
|
||||||
// Ensure that a badge appears on the button
|
// Ensure that a badge appears on the button
|
||||||
await expect(codePaneButtonHolder).toContainText('notification')
|
await expect(codePaneButtonHolder).toContainText('notification')
|
||||||
@ -235,11 +229,53 @@ extrude001 = extrude(sketch001, length = 5)`
|
|||||||
.first()
|
.first()
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('KCL errors with functions show hints for the entire backtrace', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
}) => {
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
|
const code = `fn check(@x) {
|
||||||
|
return assert(x, isGreaterThan = 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn middle(@x) {
|
||||||
|
return check(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
middle(1)
|
||||||
|
middle(0)
|
||||||
|
`
|
||||||
|
await test.step('Set the code with a KCL error', async () => {
|
||||||
|
await toolbar.openPane('code')
|
||||||
|
await editor.replaceCode('', code)
|
||||||
|
})
|
||||||
|
// This shows all the diagnostics in a way that doesn't require the mouse
|
||||||
|
// pointer hovering over a coordinate, which would be brittle.
|
||||||
|
await test.step('Open CodeMirror diagnostics list', async () => {
|
||||||
|
// Ensure keyboard focus is in the editor.
|
||||||
|
await page.getByText('fn check(').click()
|
||||||
|
await page.keyboard.press('ControlOrMeta+Shift+M')
|
||||||
|
})
|
||||||
|
await expect(
|
||||||
|
page.getByText(`assert failed: Expected 0 to be greater than 0 but it wasn't
|
||||||
|
check()
|
||||||
|
middle()`)
|
||||||
|
).toBeVisible()
|
||||||
|
// There should be one hint inside middle() and one at the top level.
|
||||||
|
await expect(page.getByText('Part of the error backtrace')).toHaveCount(2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Opening multiple panes persists when switching projects',
|
'Opening multiple panes persists when switching projects',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
// Setup multiple projects.
|
// Setup multiple projects.
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -310,7 +346,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'external change of file contents are reflected in editor',
|
'external change of file contents are reflected in editor',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const PROJECT_DIR_NAME = 'lee-was-here'
|
const PROJECT_DIR_NAME = 'lee-was-here'
|
||||||
const { dir: projectsDir } = await context.folderSetupFn(async (dir) => {
|
const { dir: projectsDir } = await context.folderSetupFn(async (dir) => {
|
||||||
|
@ -36,7 +36,10 @@ test.describe('Command bar tests', () => {
|
|||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// Click the line of code for xLine.
|
// Click the line of code for xLine.
|
||||||
await page.getByText(`close()`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
await page.getByText(`startProfile(at = [-10, -10])`).click()
|
||||||
|
|
||||||
|
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
await toolbar.extrudeButton.click()
|
await toolbar.extrudeButton.click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
@ -45,10 +48,10 @@ test.describe('Command bar tests', () => {
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Length: '',
|
Length: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -56,7 +59,7 @@ test.describe('Command bar tests', () => {
|
|||||||
stage: 'review',
|
stage: 'review',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 segment',
|
Profiles: '1 profile',
|
||||||
Length: '5',
|
Length: '5',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -286,7 +289,7 @@ test.describe('Command bar tests', () => {
|
|||||||
await cmdBar.cmdOptions.getByText('Extrude').click()
|
await cmdBar.cmdOptions.getByText('Extrude').click()
|
||||||
|
|
||||||
// Assert that we're on the selection step
|
// Assert that we're on the selection step
|
||||||
await expect(page.getByRole('button', { name: 'sketches' })).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Profiles' })).toBeDisabled()
|
||||||
// Select a face
|
// Select a face
|
||||||
await page.mouse.move(700, 200)
|
await page.mouse.move(700, 200)
|
||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
@ -399,7 +402,6 @@ test.describe('Command bar tests', () => {
|
|||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
})
|
})
|
||||||
await page.goto(page.url() + targetURL)
|
await page.goto(page.url() + targetURL)
|
||||||
expect(page.url()).toContain(targetURL)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Submit the command`, async () => {
|
await test.step(`Submit the command`, async () => {
|
||||||
@ -410,7 +412,7 @@ test.describe('Command bar tests', () => {
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: '',
|
Method: '',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'method',
|
highlightedHeaderArg: 'method',
|
||||||
@ -421,7 +423,7 @@ test.describe('Command bar tests', () => {
|
|||||||
commandName: 'Import file from URL',
|
commandName: 'Import file from URL',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: 'New project',
|
Method: 'New project',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -463,7 +465,6 @@ test.describe('Command bar tests', () => {
|
|||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
})
|
})
|
||||||
await page.goto(page.url() + targetURL)
|
await page.goto(page.url() + targetURL)
|
||||||
expect(page.url()).toContain(targetURL)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Submit the command`, async () => {
|
await test.step(`Submit the command`, async () => {
|
||||||
@ -474,7 +475,7 @@ test.describe('Command bar tests', () => {
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: '',
|
Method: '',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'method',
|
highlightedHeaderArg: 'method',
|
||||||
@ -487,7 +488,7 @@ test.describe('Command bar tests', () => {
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: 'Existing project',
|
Method: 'Existing project',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
ProjectName: '',
|
ProjectName: '',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
@ -500,7 +501,7 @@ test.describe('Command bar tests', () => {
|
|||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: 'Existing project',
|
Method: 'Existing project',
|
||||||
ProjectName: 'testProjectDir',
|
ProjectName: 'testProjectDir',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -510,7 +511,7 @@ test.describe('Command bar tests', () => {
|
|||||||
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
|
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
|
||||||
await editor.expectEditor.toContain('extrusionDistance = 12')
|
await editor.expectEditor.toContain('extrusionDistance = 12')
|
||||||
await toolbar.openPane('files')
|
await toolbar.openPane('files')
|
||||||
await toolbar.expectFileTreeState(['main.kcl', 'test.kcl'])
|
await toolbar.expectFileTreeState(['main-1.kcl', 'main.kcl'])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -518,7 +519,7 @@ test.describe('Command bar tests', () => {
|
|||||||
`Zoom to fit to shared model on web`,
|
`Zoom to fit to shared model on web`,
|
||||||
{ tag: ['@web'] },
|
{ tag: ['@web'] },
|
||||||
async ({ page, scene }) => {
|
async ({ page, scene }) => {
|
||||||
if (process.env.PLATFORM !== 'web') {
|
if (process.env.TARGET !== 'web') {
|
||||||
// This test is web-only
|
// This test is web-only
|
||||||
// TODO: re-enable on CI as part of a new @web test suite
|
// TODO: re-enable on CI as part of a new @web test suite
|
||||||
return
|
return
|
||||||
@ -661,4 +662,56 @@ c = 3 + a`
|
|||||||
`a = 5b = a * amyParameter001 = ${newValue}c = 3 + a`
|
`a = 5b = a * amyParameter001 = ${newValue}c = 3 + a`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Command palette can be opened via query parameter', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
await page.goto(`${page.url()}/?cmd=app.theme&groupId=settings`)
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: [],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
commandName: 'Settings · app · theme',
|
||||||
|
currentArgKey: 'value',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Level: 'user',
|
||||||
|
Value: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'value',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Text-to-CAD command can be closed with escape while in prompt', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: [],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
await homePage.textToCadBtn.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
commandName: 'Text-to-CAD Create',
|
||||||
|
currentArgKey: 'prompt',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Method: 'New project',
|
||||||
|
NewProjectName: 'untitled',
|
||||||
|
Prompt: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'prompt',
|
||||||
|
})
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await cmdBar.toBeClosed()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'commandBarClosed',
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'export works on the first try',
|
'export works on the first try',
|
||||||
{ tag: ['@electron', '@macos', '@windows', '@skipLocalEngine'] },
|
{ tag: ['@desktop', '@macos', '@windows', '@skipLocalEngine'] },
|
||||||
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
|
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
|
@ -1001,7 +1001,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
|
|||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`@settings(defaultLengthUnit = in)
|
`@settings(defaultLengthUnit = in)
|
||||||
sketch001 = startSketchOn(XZ)
|
sketch001 = startSketchOn(XZ)
|
||||||
|> startProfile(%, at = [3.14, 12])
|
|> startProfile(%, at = [0, 12])
|
||||||
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
|
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1076,7 +1076,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`@settings(defaultLengthUnit = in)
|
`@settings(defaultLengthUnit = in)
|
||||||
sketch001 = startSketchOn(XZ)
|
sketch001 = startSketchOn(XZ)
|
||||||
|> startProfile(%, at = [3.14, 12])
|
|> startProfile(%, at = [0, 12])
|
||||||
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
|
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -1131,6 +1131,8 @@ sketch001 = startSketchOn(XZ)
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await page.getByText('startProfile(at = [4.61, -14.01])').click()
|
await page.getByText('startProfile(at = [4.61, -14.01])').click()
|
||||||
|
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
|
||||||
|
await page.waitForTimeout(200)
|
||||||
await toolbar.extrudeButton.click()
|
await toolbar.extrudeButton.click()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
@ -1138,7 +1140,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
currentArgKey: 'length',
|
currentArgKey: 'length',
|
||||||
currentArgValue: '5',
|
currentArgValue: '5',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Length: '',
|
Length: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'length',
|
highlightedHeaderArg: 'length',
|
||||||
@ -1148,7 +1150,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Length: '5',
|
Length: '5',
|
||||||
},
|
},
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
@ -1335,7 +1337,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Can import a local OBJ file`,
|
`Can import a local OBJ file`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, context }, testInfo) => {
|
async ({ page, context }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'cube')
|
const bracketDir = join(dir, 'cube')
|
||||||
@ -1588,4 +1590,38 @@ sketch001 = startSketchOn(XZ)
|
|||||||
await expect(page.getByTestId('center-rectangle')).toBeVisible()
|
await expect(page.getByTestId('center-rectangle')).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('syntax errors still show when reopening KCL pane', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// Wait for connection, this is especially important for this test, because safeParse is invoked when
|
||||||
|
// connection is established which would interfere with the test if it happened during later steps.
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
|
// Code with no error
|
||||||
|
await u.codeLocator.fill(`x = 7`)
|
||||||
|
await page.waitForTimeout(200) // allow some time for the error to show potentially
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
|
||||||
|
|
||||||
|
// Code with error
|
||||||
|
await u.codeLocator.fill(`x 7`)
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(1)
|
||||||
|
|
||||||
|
// Close and reopen KCL code panel
|
||||||
|
await u.closeKclCodePanel()
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0) // error disappears on close
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
|
||||||
|
// Verify error is still visible
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -57,7 +57,7 @@ sketch003 = startSketchOn(plane001)
|
|||||||
test.describe('Feature Tree pane', () => {
|
test.describe('Feature Tree pane', () => {
|
||||||
test(
|
test(
|
||||||
'User can go to definition and go to function definition',
|
'User can go to definition and go to function definition',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, homePage, scene, editor, toolbar, cmdBar, page }) => {
|
async ({ context, homePage, scene, editor, toolbar, cmdBar, page }) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'test-sample')
|
const bracketDir = join(dir, 'test-sample')
|
||||||
@ -150,7 +150,7 @@ test.describe('Feature Tree pane', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`User can edit sketch (but not on offset plane yet) from the feature tree`,
|
`User can edit sketch (but not on offset plane yet) from the feature tree`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, homePage, scene, editor, toolbar, page }) => {
|
async ({ context, homePage, scene, editor, toolbar, page }) => {
|
||||||
await context.addInitScript((initialCode) => {
|
await context.addInitScript((initialCode) => {
|
||||||
localStorage.setItem('persistCode', initialCode)
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
@ -13,7 +13,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
|||||||
test.describe('integrations tests', () => {
|
test.describe('integrations tests', () => {
|
||||||
test(
|
test(
|
||||||
'Creating a new file or switching file while in sketchMode should exit sketchMode',
|
'Creating a new file or switching file while in sketchMode should exit sketchMode',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
|
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'test-sample')
|
const bracketDir = join(dir, 'test-sample')
|
||||||
@ -100,7 +100,7 @@ test.describe('when using the file tree to', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } =
|
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } =
|
||||||
await getUtils(page, test)
|
await getUtils(page, test)
|
||||||
@ -142,7 +142,7 @@ test.describe('when using the file tree to', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`create many new files of the same name, incrementing their names`,
|
`create many new files of the same name, incrementing their names`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
const { panesOpen, createNewFile } = await getUtils(page, test)
|
const { panesOpen, createNewFile } = await getUtils(page, test)
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ test.describe('when using the file tree to', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'create a new file with the same name as an existing file cancels the operation',
|
'create a new file with the same name as an existing file cancels the operation',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, homePage, scene, editor, toolbar }, testInfo) => {
|
async ({ context, page, homePage, scene, editor, toolbar }, testInfo) => {
|
||||||
const projectName = 'cube'
|
const projectName = 'cube'
|
||||||
const mainFile = 'main.kcl'
|
const mainFile = 'main.kcl'
|
||||||
@ -238,9 +238,29 @@ test.describe('when using the file tree to', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
`create new folders and that doesn't trigger a navigation`,
|
||||||
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
|
async ({ page, homePage, scene, toolbar, cmdBar }) => {
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
await toolbar.openPane('files')
|
||||||
|
const { createNewFolder } = await getUtils(page, test)
|
||||||
|
|
||||||
|
await createNewFolder('folder')
|
||||||
|
|
||||||
|
await createNewFolder('folder.kcl')
|
||||||
|
|
||||||
|
await test.step(`Postcondition: folders are created and we didn't navigate`, async () => {
|
||||||
|
await toolbar.expectFileTreeState(['folder', 'folder.kcl', 'main.kcl'])
|
||||||
|
await expect(toolbar.fileName).toHaveText('main.kcl')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'deleting all files recreates a default main.kcl with no code',
|
'deleting all files recreates a default main.kcl with no code',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
const { panesOpen, pasteCodeInEditor, deleteFile, editorTextMatches } =
|
const { panesOpen, pasteCodeInEditor, deleteFile, editorTextMatches } =
|
||||||
await getUtils(page, test)
|
await getUtils(page, test)
|
||||||
@ -271,7 +291,7 @@ test.describe('when using the file tree to', () => {
|
|||||||
test(
|
test(
|
||||||
'loading small file, then large, then back to small',
|
'loading small file, then large, then back to small',
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@desktop',
|
||||||
},
|
},
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
const {
|
const {
|
||||||
@ -341,7 +361,7 @@ test.describe('when using the file tree to', () => {
|
|||||||
test.describe('Renaming in the file tree', () => {
|
test.describe('Renaming in the file tree', () => {
|
||||||
test(
|
test(
|
||||||
'A file you have open',
|
'A file you have open',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
const { dir } = await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
@ -430,7 +450,7 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'A file you do not have open',
|
'A file you do not have open',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
const { dir } = await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
@ -516,7 +536,7 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`A folder you're not inside`,
|
`A folder you're not inside`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
const { dir } = await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
@ -598,7 +618,7 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`A folder you are inside`,
|
`A folder you are inside`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, context }, testInfo) => {
|
async ({ page, context }, testInfo) => {
|
||||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
const { dir } = await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
@ -701,7 +721,7 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
test.describe('Deleting items from the file pane', () => {
|
test.describe('Deleting items from the file pane', () => {
|
||||||
test(
|
test(
|
||||||
`delete file when main.kcl exists, navigate to main.kcl`,
|
`delete file when main.kcl exists, navigate to main.kcl`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, context }, testInfo) => {
|
async ({ page, context }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const testDir = join(dir, 'testProject')
|
const testDir = join(dir, 'testProject')
|
||||||
@ -766,7 +786,7 @@ test.describe('Deleting items from the file pane', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Delete folder we are not in, don't navigate`,
|
`Delete folder we are not in, don't navigate`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
@ -820,7 +840,7 @@ test.describe('Deleting items from the file pane', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Delete folder we are in, navigate to main.kcl`,
|
`Delete folder we are in, navigate to main.kcl`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
@ -886,7 +906,7 @@ test.describe('Deleting items from the file pane', () => {
|
|||||||
// Copied from tests above.
|
// Copied from tests above.
|
||||||
test(
|
test(
|
||||||
`external deletion of project navigates back home`,
|
`external deletion of project navigates back home`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const TEST_PROJECT_NAME = 'Test Project'
|
const TEST_PROJECT_NAME = 'Test Project'
|
||||||
const { dir: projectsDirName } = await context.folderSetupFn(
|
const { dir: projectsDirName } = await context.folderSetupFn(
|
||||||
@ -950,7 +970,7 @@ test.describe('Deleting items from the file pane', () => {
|
|||||||
// Similar to the above
|
// Similar to the above
|
||||||
test(
|
test(
|
||||||
`external deletion of file in sub-directory updates the file tree and recreates it on code editor typing`,
|
`external deletion of file in sub-directory updates the file tree and recreates it on code editor typing`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const TEST_PROJECT_NAME = 'Test Project'
|
const TEST_PROJECT_NAME = 'Test Project'
|
||||||
const { dir: projectsDirName } = await context.folderSetupFn(
|
const { dir: projectsDirName } = await context.folderSetupFn(
|
||||||
@ -1025,7 +1045,7 @@ test.describe('Deleting items from the file pane', () => {
|
|||||||
test.describe('Undo and redo do not keep history when navigating between files', () => {
|
test.describe('Undo and redo do not keep history when navigating between files', () => {
|
||||||
test(
|
test(
|
||||||
`open a file, change something, open a different file, hitting undo should do nothing`,
|
`open a file, change something, open a different file, hitting undo should do nothing`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const testDir = join(dir, 'testProject')
|
const testDir = join(dir, 'testProject')
|
||||||
@ -1092,7 +1112,7 @@ test.describe('Undo and redo do not keep history when navigating between files',
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`open a file, change something, undo it, open a different file, hitting redo should do nothing`,
|
`open a file, change something, undo it, open a different file, hitting redo should do nothing`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const testDir = join(dir, 'testProject')
|
const testDir = join(dir, 'testProject')
|
||||||
@ -1192,7 +1212,7 @@ test.describe('Undo and redo do not keep history when navigating between files',
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`cloned file has an incremented name and same contents`,
|
`cloned file has an incremented name and same contents`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, context, homePage }, testInfo) => {
|
async ({ page, context, homePage }, testInfo) => {
|
||||||
const { panesOpen, cloneFile } = await getUtils(page, test)
|
const { panesOpen, cloneFile } = await getUtils(page, test)
|
||||||
|
|
||||||
|
@ -105,14 +105,19 @@ export class CmdBarFixture {
|
|||||||
expectState = async (expected: CmdBarSerialised) => {
|
expectState = async (expected: CmdBarSerialised) => {
|
||||||
return expect.poll(() => this._serialiseCmdBar()).toEqual(expected)
|
return expect.poll(() => this._serialiseCmdBar()).toEqual(expected)
|
||||||
}
|
}
|
||||||
/** The method will use buttons OR press enter randomly to progress the cmdbar,
|
/**
|
||||||
* this could have unexpected results depending on what's focused
|
* This method is used to progress the command bar to the next step, defaulting to clicking the next button.
|
||||||
*
|
* Optionally, with the `shouldUseKeyboard` parameter, it will hit `Enter` to progress.
|
||||||
* TODO: This method assumes the user has a valid input to the current stage,
|
* * TODO: This method assumes the user has a valid input to the current stage,
|
||||||
* and assumes we are past the `pickCommand` step.
|
* and assumes we are past the `pickCommand` step.
|
||||||
*/
|
*/
|
||||||
progressCmdBar = async (shouldFuzzProgressMethod = true) => {
|
progressCmdBar = async (shouldUseKeyboard = false) => {
|
||||||
await this.page.waitForTimeout(2000)
|
await this.page.waitForTimeout(2000)
|
||||||
|
if (shouldUseKeyboard) {
|
||||||
|
await this.page.keyboard.press('Enter')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const arrowButton = this.page.getByRole('button', {
|
const arrowButton = this.page.getByRole('button', {
|
||||||
name: 'arrow right Continue',
|
name: 'arrow right Continue',
|
||||||
})
|
})
|
||||||
@ -146,9 +151,7 @@ export class CmdBarFixture {
|
|||||||
await this.cmdBarOpenBtn.click()
|
await this.cmdBarOpenBtn.click()
|
||||||
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
|
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
|
||||||
if (selectCmd === 'promptToEdit') {
|
if (selectCmd === 'promptToEdit') {
|
||||||
const promptEditCommand = this.page.getByText(
|
const promptEditCommand = this.selectOption({ name: 'Text-to-CAD Edit' })
|
||||||
'Use Zoo AI to edit your parts and code.'
|
|
||||||
)
|
|
||||||
await expect(promptEditCommand.first()).toBeVisible()
|
await expect(promptEditCommand.first()).toBeVisible()
|
||||||
await promptEditCommand.first().scrollIntoViewIfNeeded()
|
await promptEditCommand.first().scrollIntoViewIfNeeded()
|
||||||
await promptEditCommand.first().click()
|
await promptEditCommand.first().click()
|
||||||
@ -310,6 +313,11 @@ export class CmdBarFixture {
|
|||||||
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
|
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toBeClosed() {
|
||||||
|
// Check that the command bar is closed
|
||||||
|
await expect(this.cmdBarElement).not.toBeVisible({ timeout: 10_000 })
|
||||||
|
}
|
||||||
|
|
||||||
async expectArgValue(value: string) {
|
async expectArgValue(value: string) {
|
||||||
// Check the placeholder project name exists
|
// Check the placeholder project name exists
|
||||||
const actualArgument = await this.cmdBarElement
|
const actualArgument = await this.cmdBarElement
|
||||||
|
@ -394,7 +394,7 @@ const fixturesBasedOnProcessEnvPlatform = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.PLATFORM === 'web') {
|
if (process.env.TARGET === 'web') {
|
||||||
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForWeb)
|
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForWeb)
|
||||||
} else {
|
} else {
|
||||||
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForElectron)
|
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForElectron)
|
||||||
|
@ -26,6 +26,7 @@ export class HomePageFixture {
|
|||||||
sortByNameBtn!: Locator
|
sortByNameBtn!: Locator
|
||||||
appHeader!: Locator
|
appHeader!: Locator
|
||||||
tutorialBtn!: Locator
|
tutorialBtn!: Locator
|
||||||
|
textToCadBtn!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
@ -47,6 +48,7 @@ export class HomePageFixture {
|
|||||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||||
this.appHeader = this.page.getByTestId('app-header')
|
this.appHeader = this.page.getByTestId('app-header')
|
||||||
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
|
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
|
||||||
|
this.textToCadBtn = this.page.getByTestId('home-text-to-cad')
|
||||||
}
|
}
|
||||||
|
|
||||||
private _serialiseSortBy = async (): Promise<
|
private _serialiseSortBy = async (): Promise<
|
||||||
@ -121,11 +123,13 @@ export class HomePageFixture {
|
|||||||
await projectCard.click()
|
await projectCard.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
goToModelingScene = async (name: string = 'testDefault') => {
|
/** Returns the project name in case caller has used the default and needs it */
|
||||||
|
goToModelingScene = async (name = 'testDefault') => {
|
||||||
// On web this is a no-op. There is no project view.
|
// On web this is a no-op. There is no project view.
|
||||||
if (process.env.PLATFORM === 'web') return
|
if (process.env.TARGET === 'web') return ''
|
||||||
|
|
||||||
await this.createAndGoToProject(name)
|
await this.createAndGoToProject(name)
|
||||||
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
isNativeFileMenuCreated = async () => {
|
isNativeFileMenuCreated = async () => {
|
||||||
|
@ -5,7 +5,7 @@ import * as fsp from 'fs/promises'
|
|||||||
test.describe('Import UI tests', () => {
|
test.describe('Import UI tests', () => {
|
||||||
test(
|
test(
|
||||||
'shows toast when trying to sketch on imported face, and hovering over imported geometry should NOT highlight any code',
|
'shows toast when trying to sketch on imported face, and hovering over imported geometry should NOT highlight any code',
|
||||||
{ tag: ['@electron', '@macos', '@windows'] },
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
async ({ context, page, homePage, toolbar, scene, editor, cmdBar }) => {
|
async ({ context, page, homePage, toolbar, scene, editor, cmdBar }) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const projectDir = path.join(dir, 'import-test')
|
const projectDir = path.join(dir, 'import-test')
|
||||||
|
@ -61,6 +61,7 @@ class MyAPIReporter implements Reporter {
|
|||||||
const payload = {
|
const payload = {
|
||||||
// Required information
|
// Required information
|
||||||
project: 'https://github.com/KittyCAD/modeling-app',
|
project: 'https://github.com/KittyCAD/modeling-app',
|
||||||
|
suite: process.env.CI_SUITE || 'e2e',
|
||||||
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
|
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
|
||||||
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
|
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
|
||||||
test: test.titlePath().slice(2).join(' › '),
|
test: test.titlePath().slice(2).join(' › '),
|
||||||
|
@ -6,7 +6,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'When machine-api server not found butt is disabled and shows the reason',
|
'When machine-api server not found butt is disabled and shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, scene, cmdBar }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'bracket')
|
const bracketDir = join(dir, 'bracket')
|
||||||
@ -43,7 +43,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'When machine-api server not found home screen & project status shows the reason',
|
'When machine-api server not found home screen & project status shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, scene, cmdBar }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'bracket')
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
@ -13,7 +13,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
|||||||
*/
|
*/
|
||||||
test.describe(
|
test.describe(
|
||||||
'Native file menu',
|
'Native file menu',
|
||||||
{ tag: ['@electron', '@macos', '@windows'] },
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
() => {
|
() => {
|
||||||
test('Home page', async ({ tronApp, cmdBar, page, homePage }) => {
|
test('Home page', async ({ tronApp, cmdBar, page, homePage }) => {
|
||||||
if (!tronApp) fail()
|
if (!tronApp) fail()
|
||||||
@ -252,7 +252,7 @@ test.describe(
|
|||||||
tronApp,
|
tronApp,
|
||||||
'Edit.Modify with Zoo Text-To-CAD'
|
'Edit.Modify with Zoo Text-To-CAD'
|
||||||
)
|
)
|
||||||
await cmdBar.expectCommandName('Prompt-to-edit')
|
await cmdBar.expectCommandName('Text-to-CAD Edit')
|
||||||
})
|
})
|
||||||
await test.step('Modeling.Edit.Edit parameter', async () => {
|
await test.step('Modeling.Edit.Edit parameter', async () => {
|
||||||
await page.waitForTimeout(250)
|
await page.waitForTimeout(250)
|
||||||
@ -518,7 +518,7 @@ test.describe(
|
|||||||
'Design.Create with Zoo Text-To-CAD'
|
'Design.Create with Zoo Text-To-CAD'
|
||||||
)
|
)
|
||||||
await cmdBar.toBeOpened()
|
await cmdBar.toBeOpened()
|
||||||
await cmdBar.expectCommandName('Text to CAD')
|
await cmdBar.expectCommandName('Text-to-CAD Create')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Modeling.Design.Modify with Zoo Text-To-CAD', async () => {
|
await test.step('Modeling.Design.Modify with Zoo Text-To-CAD', async () => {
|
||||||
@ -528,7 +528,7 @@ test.describe(
|
|||||||
'Design.Modify with Zoo Text-To-CAD'
|
'Design.Modify with Zoo Text-To-CAD'
|
||||||
)
|
)
|
||||||
await cmdBar.toBeOpened()
|
await cmdBar.toBeOpened()
|
||||||
await cmdBar.expectCommandName('Prompt-to-edit')
|
await cmdBar.expectCommandName('Text-to-CAD Edit')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Modeling.Help.KCL code samples', async () => {
|
await test.step('Modeling.Help.KCL code samples', async () => {
|
||||||
|
@ -43,7 +43,7 @@ async function insertPartIntoAssembly(
|
|||||||
test.describe('Point-and-click assemblies tests', () => {
|
test.describe('Point-and-click assemblies tests', () => {
|
||||||
test(
|
test(
|
||||||
`Insert kcl parts into assembly as whole module import`,
|
`Insert kcl parts into assembly as whole module import`,
|
||||||
{ tag: ['@electron', '@macos', '@windows'] },
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -70,22 +70,28 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
await test.step('Setup parts and expect empty assembly scene', async () => {
|
await test.step('Setup parts and expect empty assembly scene', async () => {
|
||||||
const projectName = 'assembly'
|
const projectName = 'assembly'
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, projectName)
|
const projDir = path.join(dir, projectName)
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
const nestedProjDir = path.join(dir, projectName, 'nested', 'twice')
|
||||||
|
await fsp.mkdir(projDir, { recursive: true })
|
||||||
|
await fsp.mkdir(nestedProjDir, { recursive: true })
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
executorInputPath('cylinder.kcl'),
|
executorInputPath('cylinder.kcl'),
|
||||||
path.join(bracketDir, 'cylinder.kcl')
|
path.join(projDir, 'cylinder.kcl')
|
||||||
|
),
|
||||||
|
fsp.copyFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
path.join(nestedProjDir, 'main.kcl')
|
||||||
),
|
),
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
|
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
|
||||||
path.join(bracketDir, 'bracket.kcl')
|
path.join(projDir, 'bracket.kcl')
|
||||||
),
|
),
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
testsInputPath('cube.step'),
|
testsInputPath('cube.step'),
|
||||||
path.join(bracketDir, 'cube.step')
|
path.join(projDir, 'cube.step')
|
||||||
),
|
),
|
||||||
fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''),
|
fsp.writeFile(path.join(projDir, 'main.kcl'), ''),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
@ -167,13 +173,32 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
await expect(
|
await expect(
|
||||||
page.getByText('This file is already imported')
|
page.getByText('This file is already imported')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
await cmdBar.closeCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Insert a nested kcl part', async () => {
|
||||||
|
await insertPartIntoAssembly(
|
||||||
|
'nested/twice/main.kcl',
|
||||||
|
'main',
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
page
|
||||||
|
)
|
||||||
|
await toolbar.openPane('code')
|
||||||
|
await page.waitForTimeout(10000)
|
||||||
|
await editor.expectEditor.toContain(
|
||||||
|
`
|
||||||
|
import "nested/twice/main.kcl" as main
|
||||||
|
`,
|
||||||
|
{ shouldNormalise: true }
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
`Can still translate, rotate, and delete inserted parts even with non standard code`,
|
`Can still translate, rotate, and delete inserted parts even with non standard code`,
|
||||||
{ tag: ['@electron', '@macos', '@windows'] },
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -369,7 +394,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Insert the bracket part into an assembly and transform it`,
|
`Insert the bracket part into an assembly and transform it`,
|
||||||
{ tag: ['@electron', '@macos', '@windows'] },
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -560,7 +585,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Insert foreign parts into assembly and delete them`,
|
`Insert foreign parts into assembly and delete them`,
|
||||||
{ tag: ['@electron', '@macos', '@windows'] },
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -711,7 +736,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Assembly gets reexecuted when imported models are updated externally',
|
'Assembly gets reexecuted when imported models are updated externally',
|
||||||
{ tag: ['@electron', '@macos', '@windows'] },
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
async ({ context, page, homePage, scene, toolbar, cmdBar, tronApp }) => {
|
async ({ context, page, homePage, scene, toolbar, cmdBar, tronApp }) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
@ -801,7 +826,7 @@ foreign
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Point-and-click clone`,
|
`Point-and-click clone`,
|
||||||
{ tag: ['@electron', '@macos', '@windows'] },
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
async ({
|
async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
@ -78,8 +78,8 @@ test.describe('Point-and-click tests', () => {
|
|||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: { Sketches: '', Length: '' },
|
headerArguments: { Profiles: '', Length: '' },
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -87,7 +87,7 @@ test.describe('Point-and-click tests', () => {
|
|||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'length',
|
currentArgKey: 'length',
|
||||||
currentArgValue: '5',
|
currentArgValue: '5',
|
||||||
headerArguments: { Sketches: '1 face', Length: '' },
|
headerArguments: { Profiles: '1 profile', Length: '' },
|
||||||
highlightedHeaderArg: 'length',
|
highlightedHeaderArg: 'length',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
@ -98,7 +98,7 @@ test.describe('Point-and-click tests', () => {
|
|||||||
|
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: { Sketches: '1 face', Length: '5' },
|
headerArguments: { Profiles: '1 profile', Length: '5' },
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -1634,15 +1634,15 @@ sketch002 = startSketchOn(plane001)
|
|||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: { Sketches: '' },
|
headerArguments: { Profiles: '' },
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Loft',
|
commandName: 'Loft',
|
||||||
})
|
})
|
||||||
await selectSketches()
|
await selectSketches()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: { Sketches: '2 faces' },
|
headerArguments: { Profiles: '2 profiles' },
|
||||||
commandName: 'Loft',
|
commandName: 'Loft',
|
||||||
})
|
})
|
||||||
await cmdBar.submit()
|
await cmdBar.submit()
|
||||||
@ -1658,14 +1658,14 @@ sketch002 = startSketchOn(plane001)
|
|||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: { Sketches: '' },
|
headerArguments: { Profiles: '' },
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Loft',
|
commandName: 'Loft',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: { Sketches: '2 faces' },
|
headerArguments: { Profiles: '2 profiles' },
|
||||||
commandName: 'Loft',
|
commandName: 'Loft',
|
||||||
})
|
})
|
||||||
await cmdBar.submit()
|
await cmdBar.submit()
|
||||||
@ -1830,10 +1830,10 @@ sketch002 = startSketchOn(XZ)
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await clickOnSketch1()
|
await clickOnSketch1()
|
||||||
@ -1844,7 +1844,7 @@ sketch002 = startSketchOn(XZ)
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'path',
|
||||||
@ -1857,7 +1857,7 @@ sketch002 = startSketchOn(XZ)
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'path',
|
||||||
@ -1867,13 +1867,17 @@ sketch002 = startSketchOn(XZ)
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '1 segment',
|
Path: '1 segment',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
// Confirm we can submit from the review step with just `Enter`
|
||||||
|
await cmdBar.progressCmdBar(true)
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'commandBarClosed',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
@ -1968,10 +1972,10 @@ profile001 = ${circleCode}`
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await editor.scrollToText(circleCode)
|
await editor.scrollToText(circleCode)
|
||||||
@ -1983,7 +1987,7 @@ profile001 = ${circleCode}`
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'path',
|
||||||
@ -1997,7 +2001,7 @@ profile001 = ${circleCode}`
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'path',
|
||||||
@ -2007,13 +2011,13 @@ profile001 = ${circleCode}`
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '1 helix',
|
Path: '1 helix',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar(true)
|
||||||
await editor.expectEditor.toContain(sweepDeclaration)
|
await editor.expectEditor.toContain(sweepDeclaration)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -3691,7 +3695,7 @@ tag=$rectangleSegmentC002,
|
|||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// select line of code
|
// select line of code
|
||||||
const codeToSelection = `segAng(rectangleSegmentA002) - 90,`
|
const codeToSelection = `startProfile(at = [-66.77, 84.81])`
|
||||||
// revolve
|
// revolve
|
||||||
await editor.scrollToText(codeToSelection)
|
await editor.scrollToText(codeToSelection)
|
||||||
await page.getByText(codeToSelection).click()
|
await page.getByText(codeToSelection).click()
|
||||||
@ -4634,10 +4638,10 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Length: '',
|
Length: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -4646,7 +4650,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'length',
|
currentArgKey: 'length',
|
||||||
currentArgValue: '5',
|
currentArgValue: '5',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
Length: '',
|
Length: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'length',
|
highlightedHeaderArg: 'length',
|
||||||
@ -4657,7 +4661,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
Length: '1',
|
Length: '1',
|
||||||
},
|
},
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
@ -4728,11 +4732,11 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Path: '',
|
Path: '',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -4741,7 +4745,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'path',
|
currentArgKey: 'path',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
Path: '',
|
Path: '',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
@ -4754,7 +4758,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
Path: '1 segment',
|
Path: '1 segment',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
@ -4825,11 +4829,11 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
AxisOrEdge: '',
|
AxisOrEdge: '',
|
||||||
Angle: '',
|
Angle: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Revolve',
|
commandName: 'Revolve',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -4838,7 +4842,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'axisOrEdge',
|
currentArgKey: 'axisOrEdge',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
AxisOrEdge: '',
|
AxisOrEdge: '',
|
||||||
Angle: '',
|
Angle: '',
|
||||||
},
|
},
|
||||||
@ -4854,7 +4858,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'angle',
|
currentArgKey: 'angle',
|
||||||
currentArgValue: '360',
|
currentArgValue: '360',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
AxisOrEdge: 'Edge',
|
AxisOrEdge: 'Edge',
|
||||||
Edge: '1 segment',
|
Edge: '1 segment',
|
||||||
Angle: '',
|
Angle: '',
|
||||||
@ -4867,7 +4871,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
AxisOrEdge: 'Edge',
|
AxisOrEdge: 'Edge',
|
||||||
Edge: '1 segment',
|
Edge: '1 segment',
|
||||||
Angle: '180',
|
Angle: '180',
|
||||||
|
@ -11,12 +11,13 @@ import {
|
|||||||
getPlaywrightDownloadDir,
|
getPlaywrightDownloadDir,
|
||||||
getUtils,
|
getUtils,
|
||||||
isOutOfViewInScrollContainer,
|
isOutOfViewInScrollContainer,
|
||||||
|
runningOnWindows,
|
||||||
} from '@e2e/playwright/test-utils'
|
} from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'projects reload if a new one is created, deleted, or renamed externally',
|
'projects reload if a new one is created, deleted, or renamed externally',
|
||||||
{ tag: ['@electron', '@macos', '@windows'] },
|
{ tag: ['@desktop', '@macos', '@windows'] },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
let externalCreatedProjectName = 'external-created-project'
|
let externalCreatedProjectName = 'external-created-project'
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'click help/keybindings from home page',
|
'click help/keybindings from home page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -80,7 +81,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'click help/keybindings from project page',
|
'click help/keybindings from project page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ scene, cmdBar, context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
@ -111,7 +112,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ scene, cmdBar, context, page, editor }, testInfo) => {
|
async ({ scene, cmdBar, context, page, editor }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
@ -183,7 +184,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ scene, cmdBar, context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
@ -243,7 +244,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open empty file, it should clear the scene',
|
'open a file in a project works and renders, open empty file, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
@ -309,7 +310,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
|
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ scene, cmdBar, context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
@ -381,7 +382,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'when code with error first loads you get errors in console',
|
'when code with error first loads you get errors in console',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, editor }, testInfo) => {
|
async ({ context, page, editor }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(path.join(dir, 'broken-code'), { recursive: true })
|
await fsp.mkdir(path.join(dir, 'broken-code'), { recursive: true })
|
||||||
@ -415,7 +416,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
for (const method of exportMethods) {
|
for (const method of exportMethods) {
|
||||||
test(
|
test(
|
||||||
`Can export using ${method}`,
|
`Can export using ${method}`,
|
||||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
{ tag: ['@desktop', '@skipLocalEngine'] },
|
||||||
async ({ scene, cmdBar, context, page, tronApp }, testInfo) => {
|
async ({ scene, cmdBar, context, page, tronApp }, testInfo) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
@ -506,7 +507,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
})
|
})
|
||||||
test(
|
test(
|
||||||
'Rename and delete projects, also spam arrow keys when renaming',
|
'Rename and delete projects, also spam arrow keys when renaming',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
|
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
|
||||||
@ -722,7 +723,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'pressing "delete" on home screen should do nothing',
|
'pressing "delete" on home screen should do nothing',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, homePage }, testInfo) => {
|
async ({ context, page, homePage }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
|
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
|
||||||
@ -752,7 +753,7 @@ test(
|
|||||||
test.describe(`Project management commands`, () => {
|
test.describe(`Project management commands`, () => {
|
||||||
test(
|
test(
|
||||||
`Rename from project page`,
|
`Rename from project page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, scene, cmdBar }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
const projectName = `my_project_to_rename`
|
const projectName = `my_project_to_rename`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -814,7 +815,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Delete from project page`,
|
`Delete from project page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, scene, cmdBar }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
const projectName = `my_project_to_delete`
|
const projectName = `my_project_to_delete`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -868,7 +869,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
)
|
)
|
||||||
test(
|
test(
|
||||||
`Rename from home page`,
|
`Rename from home page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, homePage, scene, cmdBar }, testInfo) => {
|
async ({ context, page, homePage, scene, cmdBar }, testInfo) => {
|
||||||
const projectName = `my_project_to_rename`
|
const projectName = `my_project_to_rename`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -926,7 +927,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
)
|
)
|
||||||
test(
|
test(
|
||||||
`Delete from home page`,
|
`Delete from home page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, scene, cmdBar }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
const projectName = `my_project_to_delete`
|
const projectName = `my_project_to_delete`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -1102,7 +1103,7 @@ test(`Create a few projects using the default project name`, async ({
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'File in the file pane should open with a single click',
|
'File in the file pane should open with a single click',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, homePage, page }, testInfo) => {
|
async ({ context, homePage, page }, testInfo) => {
|
||||||
const projectName = 'router-template-slate'
|
const projectName = 'router-template-slate'
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -1144,7 +1145,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Nested directories in project without main.kcl do not create main.kcl',
|
'Nested directories in project without main.kcl do not create main.kcl',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ scene, cmdBar, context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
let testDir: string | undefined
|
let testDir: string | undefined
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -1201,7 +1202,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const projectData = [
|
const projectData = [
|
||||||
['router-template-slate', 'cylinder.kcl'],
|
['router-template-slate', 'cylinder.kcl'],
|
||||||
@ -1279,7 +1280,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Can load a file with CRLF line endings',
|
'Can load a file with CRLF line endings',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, scene, cmdBar }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const routerTemplateDir = path.join(dir, 'router-template-slate')
|
const routerTemplateDir = path.join(dir, 'router-template-slate')
|
||||||
@ -1311,7 +1312,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Can sort projects on home page',
|
'Can sort projects on home page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const projectData = [
|
const projectData = [
|
||||||
['router-template-slate', 'cylinder.kcl'],
|
['router-template-slate', 'cylinder.kcl'],
|
||||||
@ -1418,7 +1419,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'When the project folder is empty, user can create new project and open it.',
|
'When the project folder is empty, user can create new project and open it.',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -1514,7 +1515,7 @@ extrude001 = extrude(sketch001, length = 200)`)
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, cmdBar, homePage, scene }, testInfo) => {
|
async ({ context, page, cmdBar, homePage, scene }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(path.join(dir, 'router-template-slate'), {
|
await fsp.mkdir(path.join(dir, 'router-template-slate'), {
|
||||||
@ -1627,7 +1628,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'You can change the root projects directory and nothing is lost',
|
'You can change the root projects directory and nothing is lost',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page, tronApp, homePage }, testInfo) => {
|
async ({ context, page, tronApp, homePage }, testInfo) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
@ -1734,7 +1735,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Search projects on desktop home',
|
'Search projects on desktop home',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const projectData = [
|
const projectData = [
|
||||||
['basic bracket', 'focusrite_scarlett_mounting_bracket.kcl'],
|
['basic bracket', 'focusrite_scarlett_mounting_bracket.kcl'],
|
||||||
@ -1790,7 +1791,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'file pane is scrollable when there are many files',
|
'file pane is scrollable when there are many files',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ scene, cmdBar, context, page }, testInfo) => {
|
async ({ scene, cmdBar, context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const testDir = path.join(dir, 'testProject')
|
const testDir = path.join(dir, 'testProject')
|
||||||
@ -1891,7 +1892,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'select all in code editor does not actually select all, just what is visible (regression)',
|
'select all in code editor does not actually select all, just what is visible (regression)',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
// rust/kcl-lib/e2e/executor/inputs/mike_stress_test.kcl
|
// rust/kcl-lib/e2e/executor/inputs/mike_stress_test.kcl
|
||||||
@ -1949,7 +1950,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Settings persist across restarts',
|
'Settings persist across restarts',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, toolbar }, testInfo) => {
|
async ({ page, toolbar }, testInfo) => {
|
||||||
await test.step('We can change a user setting like theme', async () => {
|
await test.step('We can change a user setting like theme', async () => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -1979,10 +1980,9 @@ test(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flaky
|
|
||||||
test(
|
test(
|
||||||
'Original project name persist after onboarding',
|
'Original project name persist after onboarding',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, toolbar }, testInfo) => {
|
async ({ page, toolbar }, testInfo) => {
|
||||||
const nextButton = page.getByTestId('onboarding-next')
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -2022,7 +2022,7 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'project name with foreign characters should open',
|
'project name with foreign characters should open',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'العربية')
|
const bracketDir = path.join(dir, 'العربية')
|
||||||
@ -2064,3 +2064,55 @@ test(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'import from nested directory',
|
||||||
|
{ tag: ['@desktop', '@windows', '@macos'] },
|
||||||
|
async ({ scene, cmdBar, context, page }) => {
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
|
const nestedDir = path.join(bracketDir, 'nested')
|
||||||
|
await fsp.mkdir(nestedDir, { recursive: true })
|
||||||
|
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cylinder-inches.kcl'),
|
||||||
|
path.join(nestedDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.writeFile(
|
||||||
|
path.join(bracketDir, 'main.kcl'),
|
||||||
|
runningOnWindows()
|
||||||
|
? `import 'nested\\main.kcl' as thing\n\nthing`
|
||||||
|
: `import 'nested/main.kcl' as thing\n\nthing`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
const pointOnModel = { x: 630, y: 280 }
|
||||||
|
|
||||||
|
await test.step('Opening the bracket project should load the stream', async () => {
|
||||||
|
// expect to see the text bracket
|
||||||
|
await expect(page.getByText('bracket')).toBeVisible()
|
||||||
|
|
||||||
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).toBeEnabled({
|
||||||
|
timeout: 20_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// gray at this pixel means the stream has loaded in the most
|
||||||
|
// user way we can verify it (pixel color)
|
||||||
|
await expect
|
||||||
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
.toBeLessThan(15)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -63,7 +63,7 @@ test.describe('edit with AI example snapshots', () => {
|
|||||||
test(
|
test(
|
||||||
`change colour`,
|
`change colour`,
|
||||||
// TODO this is more of a snapshot, but atm it needs to be manually run locally to update the files
|
// TODO this is more of a snapshot, but atm it needs to be manually run locally to update the files
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@desktop'] },
|
||||||
async ({ context, homePage, cmdBar, editor, page, scene }) => {
|
async ({ context, homePage, cmdBar, editor, page, scene }) => {
|
||||||
const project = 'test-dir'
|
const project = 'test-dir'
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
@ -558,7 +558,7 @@ extrude002 = extrude(profile002, length = 150)
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Network health indicator only appears in modeling view`,
|
`Network health indicator only appears in modeling view`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }) => {
|
async ({ context, page }) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
|
@ -17,7 +17,7 @@ test.describe('Share link tests', () => {
|
|||||||
`Open in desktop app with ${codeLength}-long code ${isWindows && showsErrorOnWindows ? 'shows error' : "doesn't show error"}`,
|
`Open in desktop app with ${codeLength}-long code ${isWindows && showsErrorOnWindows ? 'shows error' : "doesn't show error"}`,
|
||||||
{ tag: ['@web'] },
|
{ tag: ['@web'] },
|
||||||
async ({ page }) => {
|
async ({ page }) => {
|
||||||
if (process.env.PLATFORM !== 'web') {
|
if (process.env.TARGET !== 'web') {
|
||||||
// This test is web-only
|
// This test is web-only
|
||||||
// TODO: re-enable on CI as part of a new @web test suite
|
// TODO: re-enable on CI as part of a new @web test suite
|
||||||
return
|
return
|
||||||
|
@ -995,8 +995,8 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
|
|||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// click "line(end = [1.32, 0.38])"
|
// click profile in code
|
||||||
await page.getByText(`line(end = [1.32, 0.38])`).click()
|
await page.getByText(`startProfile(at = [-0.45, 0.87])`).click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
||||||
{ timeout: 10_000 }
|
{ timeout: 10_000 }
|
||||||
@ -1014,14 +1014,14 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
|
|||||||
// click extrude
|
// click extrude
|
||||||
await toolbar.extrudeButton.click()
|
await toolbar.extrudeButton.click()
|
||||||
|
|
||||||
// sketch selection should already have been made. "Sketches: 1 face" only show up when the selection has been made already
|
// sketch selection should already have been made.
|
||||||
// otherwise the cmdbar would be waiting for a selection.
|
// otherwise the cmdbar would be waiting for a selection.
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'length',
|
currentArgKey: 'length',
|
||||||
currentArgValue: '5',
|
currentArgValue: '5',
|
||||||
headerArguments: { Sketches: '1 segment', Length: '' },
|
headerArguments: { Profiles: '1 profile', Length: '' },
|
||||||
highlightedHeaderArg: 'length',
|
highlightedHeaderArg: 'length',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 64 KiB |
@ -279,7 +279,7 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34])
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Paused stream freezes view frame, unpause reconnect is seamless to user',
|
'Paused stream freezes view frame, unpause reconnect is seamless to user',
|
||||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
{ tag: ['@desktop', '@skipLocalEngine'] },
|
||||||
async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => {
|
async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => {
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
const networkToggleConnectedText = page.getByText('Connected')
|
const networkToggleConnectedText = page.getByText('Connected')
|
||||||
|
@ -79,20 +79,6 @@ export function runningOnWindows() {
|
|||||||
return process.platform === 'win32'
|
return process.platform === 'win32'
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForPageLoadWithRetry(page: Page) {
|
|
||||||
await expect(async () => {
|
|
||||||
await page.goto('/')
|
|
||||||
const errorMessage = 'App failed to load - 🔃 Retrying ...'
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'sketch Start Sketch' }),
|
|
||||||
errorMessage
|
|
||||||
).toBeEnabled({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
}).toPass({ timeout: 70_000, intervals: [1_000] })
|
|
||||||
}
|
|
||||||
|
|
||||||
// lee: This needs to be replaced by scene.settled() eventually.
|
// lee: This needs to be replaced by scene.settled() eventually.
|
||||||
async function waitForPageLoad(page: Page) {
|
async function waitForPageLoad(page: Page) {
|
||||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeEnabled({
|
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeEnabled({
|
||||||
@ -354,13 +340,8 @@ async function waitForAuthAndLsp(page: Page) {
|
|||||||
},
|
},
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
})
|
})
|
||||||
if (process.env.CI) {
|
await page.goto('/')
|
||||||
await waitForPageLoadWithRetry(page)
|
await waitForPageLoad(page)
|
||||||
} else {
|
|
||||||
await page.goto('/')
|
|
||||||
await waitForPageLoad(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
return waitForLspPromise
|
return waitForLspPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,7 +372,6 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
const util = {
|
const util = {
|
||||||
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]) => {
|
||||||
@ -557,6 +537,14 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createNewFolder: async (name: string) => {
|
||||||
|
return test?.step(`Create a folder named ${name}`, async () => {
|
||||||
|
await page.getByTestId('create-folder-button').click()
|
||||||
|
await page.getByTestId('tree-input-field').fill(name)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
cloneFile: async (name: string) => {
|
cloneFile: async (name: string) => {
|
||||||
return test?.step(`Cloning file '${name}'`, async () => {
|
return test?.step(`Cloning file '${name}'`, async () => {
|
||||||
await page
|
await page
|
||||||
|
@ -1120,7 +1120,7 @@ part002 = startSketchOn(XZ)
|
|||||||
test.describe('Electron constraint tests', () => {
|
test.describe('Electron constraint tests', () => {
|
||||||
test(
|
test(
|
||||||
'Able to double click label to set constraint',
|
'Able to double click label to set constraint',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
|
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'test-sample')
|
const bracketDir = path.join(dir, 'test-sample')
|
||||||
|
@ -84,7 +84,7 @@ test.describe('Testing loading external models', () => {
|
|||||||
*/
|
*/
|
||||||
test(
|
test(
|
||||||
'Desktop: should create new file by default, creates a second file with automatic unique name',
|
'Desktop: should create new file by default, creates a second file with automatic unique name',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ editor, context, page, scene, cmdBar, toolbar }) => {
|
async ({ editor, context, page, scene, cmdBar, toolbar }) => {
|
||||||
if (runningOnWindows()) {
|
if (runningOnWindows()) {
|
||||||
}
|
}
|
||||||
@ -103,6 +103,8 @@ test.describe('Testing loading external models', () => {
|
|||||||
file: 'ball-bearing' + FILE_EXT,
|
file: 'ball-bearing' + FILE_EXT,
|
||||||
title: 'Ball Bearing',
|
title: 'Ball Bearing',
|
||||||
file1: 'ball-bearing-1' + FILE_EXT,
|
file1: 'ball-bearing-1' + FILE_EXT,
|
||||||
|
folderName: 'ball-bearing',
|
||||||
|
folderName1: 'ball-bearing-1',
|
||||||
}
|
}
|
||||||
const projectCard = page.getByRole('link', { name: 'bracket' })
|
const projectCard = page.getByRole('link', { name: 'bracket' })
|
||||||
const overwriteWarning = page.getByText(
|
const overwriteWarning = page.getByText(
|
||||||
@ -154,8 +156,10 @@ test.describe('Testing loading external models', () => {
|
|||||||
|
|
||||||
await test.step(`Ensure we made and opened a new file`, async () => {
|
await test.step(`Ensure we made and opened a new file`, async () => {
|
||||||
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
||||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
await expect(
|
||||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
page.getByTestId('file-tree-item').getByText(sampleOne.folderName)
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||||
@ -169,8 +173,10 @@ test.describe('Testing loading external models', () => {
|
|||||||
|
|
||||||
await test.step(`Ensure we made and opened a new file with a unique name`, async () => {
|
await test.step(`Ensure we made and opened a new file with a unique name`, async () => {
|
||||||
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
||||||
await expect(newlyCreatedFile(sampleOne.file1)).toBeVisible()
|
await expect(
|
||||||
await expect(projectMenuButton).toContainText(sampleOne.file1)
|
page.getByTestId('file-tree-item').getByText(sampleOne.folderName1)
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -190,7 +196,7 @@ test.describe('Testing loading external models', () => {
|
|||||||
externalModelCases.map(({ modelName, deconflictedModelName, modelPath }) => {
|
externalModelCases.map(({ modelName, deconflictedModelName, modelPath }) => {
|
||||||
test(
|
test(
|
||||||
`Load external models from local drive - ${modelName}`,
|
`Load external models from local drive - ${modelName}`,
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@desktop'] },
|
||||||
async ({ page, homePage, scene, toolbar, cmdBar, tronApp }) => {
|
async ({ page, homePage, scene, toolbar, cmdBar, tronApp }) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
|
@ -278,7 +278,7 @@ test.describe(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Project settings override user settings on desktop`,
|
`Project settings override user settings on desktop`,
|
||||||
{ tag: ['@electron'] },
|
{ tag: ['@desktop'] },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const projectName = 'bracket'
|
const projectName = 'bracket'
|
||||||
const { dir: projectDirName } = await context.folderSetupFn(
|
const { dir: projectDirName } = await context.folderSetupFn(
|
||||||
@ -373,7 +373,7 @@ test.describe(
|
|||||||
test(
|
test(
|
||||||
`Load desktop app with no settings file`,
|
`Load desktop app with no settings file`,
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@desktop',
|
||||||
},
|
},
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -393,7 +393,7 @@ test.describe(
|
|||||||
test(
|
test(
|
||||||
`Load desktop app with a settings file, but no project directory setting`,
|
`Load desktop app with a settings file, but no project directory setting`,
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@desktop',
|
||||||
},
|
},
|
||||||
async ({ context, page, tronApp }, testInfo) => {
|
async ({ context, page, tronApp }, testInfo) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
@ -425,7 +425,7 @@ test.describe(
|
|||||||
test(
|
test(
|
||||||
'user settings reload on external change, on project and modeling view',
|
'user settings reload on external change, on project and modeling view',
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@desktop',
|
||||||
},
|
},
|
||||||
async ({ context, page, tronApp }, testInfo) => {
|
async ({ context, page, tronApp }, testInfo) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
@ -486,7 +486,7 @@ test.describe(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'project settings reload on external change',
|
'project settings reload on external change',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { dir: projectDirName } = await context.folderSetupFn(
|
const { dir: projectDirName } = await context.folderSetupFn(
|
||||||
async () => {}
|
async () => {}
|
||||||
@ -536,7 +536,7 @@ test.describe(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
`Closing settings modal should go back to the original file being viewed`,
|
`Closing settings modal should go back to the original file being viewed`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'project-000')
|
const bracketDir = join(dir, 'project-000')
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import { join } from 'path'
|
|
||||||
import type { Page } from '@playwright/test'
|
import type { Page } from '@playwright/test'
|
||||||
|
|
||||||
import { createProject, getUtils } from '@e2e/playwright/test-utils'
|
import { createProject, getUtils } from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||||
|
|
||||||
test.describe('Text-to-CAD tests', () => {
|
test.describe('Text-to-CAD tests', () => {
|
||||||
test('basic lego happy case', async ({ page, homePage }) => {
|
test('basic lego happy case', async ({ page, homePage, cmdBar }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await test.step('Set up', async () => {
|
await test.step('Set up', async () => {
|
||||||
@ -15,7 +14,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -56,6 +59,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
|
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -64,7 +68,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x6 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x6 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -82,7 +90,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
// Can send a new prompt from the command bar.
|
// Can send a new prompt from the command bar.
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -100,6 +112,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('you can reject text-to-cad output and it does nothing', async ({
|
test('you can reject text-to-cad output and it does nothing', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -108,7 +121,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -141,6 +158,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('sending a bad prompt fails, can dismiss', async ({
|
test('sending a bad prompt fails, can dismiss', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -150,7 +168,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
|
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, randomPrompt)
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
randomPrompt,
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -188,6 +210,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('sending a bad prompt fails, can start over from toast', async ({
|
test('sending a bad prompt fails, can start over from toast', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -197,7 +220,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -256,6 +279,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -265,7 +289,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -292,7 +316,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||||
|
|
||||||
// They should be able to try again from the command bar.
|
// They should be able to try again from the command bar.
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -310,17 +338,40 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('ensure you can shift+enter in the prompt box', async ({
|
test('ensure you can shift+enter in the prompt box', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
const projectName = await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const promptWithNewline = `a 2x4\nlego`
|
const promptWithNewline = `a 2x4\nlego`
|
||||||
|
|
||||||
await page.getByTestId('text-to-cad').click()
|
await test.step('Get to the prompt step to test', async () => {
|
||||||
|
await cmdBar.openCmdBar()
|
||||||
|
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
|
||||||
|
|
||||||
|
await cmdBar.currentArgumentInput.fill('existing')
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
|
await cmdBar.currentArgumentInput.fill(projectName)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Text-to-CAD Create',
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'prompt',
|
||||||
|
currentArgValue: '',
|
||||||
|
highlightedHeaderArg: 'prompt',
|
||||||
|
headerArguments: {
|
||||||
|
Method: 'Existing project',
|
||||||
|
ProjectName: projectName,
|
||||||
|
Prompt: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
await page.keyboard.type('a 2x4')
|
await page.keyboard.type('a 2x4')
|
||||||
@ -350,96 +401,10 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(page.getByText(promptWithNewline)).toBeVisible()
|
await expect(page.getByText(promptWithNewline)).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
// This will be fine once greg makes prompt at top of file deterministic
|
|
||||||
test('can do many at once and get many prompts back, and interact with many', async ({
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
}) => {
|
|
||||||
// Let this test run longer since we've seen it timeout.
|
|
||||||
test.setTimeout(180_000)
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
await u.waitForPageLoad()
|
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x8 lego')
|
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x10 lego')
|
|
||||||
|
|
||||||
// Find the toast.
|
|
||||||
// Look out for the toast message
|
|
||||||
const submittingToastMessage = page.getByText(
|
|
||||||
`Submitting to Text-to-CAD API...`
|
|
||||||
)
|
|
||||||
await expect(submittingToastMessage.first()).toBeVisible()
|
|
||||||
|
|
||||||
const generatingToastMessage = page.getByText(
|
|
||||||
`Generating parametric model...`
|
|
||||||
)
|
|
||||||
await expect(generatingToastMessage.first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
|
||||||
// We should have three success toasts.
|
|
||||||
await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 })
|
|
||||||
|
|
||||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
|
||||||
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
|
|
||||||
await expect(page.getByText(`a 2x10 lego`)).toBeVisible()
|
|
||||||
|
|
||||||
// Ensure if you reject one, the others stay.
|
|
||||||
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
|
||||||
await expect(rejectButton.first()).toBeVisible()
|
|
||||||
// Click the reject button on the first toast.
|
|
||||||
await rejectButton.first().click()
|
|
||||||
|
|
||||||
// The first toast should disappear, but not the others.
|
|
||||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
|
||||||
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
|
|
||||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
|
||||||
|
|
||||||
// Ensure you can copy the code for one of the models remaining.
|
|
||||||
const copyToClipboardButton = page.getByRole('button', {
|
|
||||||
name: 'Accept',
|
|
||||||
})
|
|
||||||
await expect(copyToClipboardButton.first()).toBeVisible()
|
|
||||||
// Click the button.
|
|
||||||
await copyToClipboardButton.first().click()
|
|
||||||
|
|
||||||
// Do NOT do AI tests like this: "Expect the code to be pasted."
|
|
||||||
// Reason: AI tests are NONDETERMINISTIC. Thus we need to be as most
|
|
||||||
// general as we can for the assertion.
|
|
||||||
// We can use Kolmogorov complexity as a measurement of the
|
|
||||||
// "probably most minimal version of this program" to have a lower
|
|
||||||
// bound to work with. It is completely by feel because there are
|
|
||||||
// no proofs that any program is its smallest self.
|
|
||||||
const code2x8 = await page.locator('.cm-content').innerText()
|
|
||||||
await expect(code2x8.length).toBeGreaterThan(249)
|
|
||||||
|
|
||||||
// Ensure the final toast remains.
|
|
||||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
|
||||||
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
|
|
||||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
|
||||||
|
|
||||||
// Ensure you can copy the code for the final model.
|
|
||||||
await expect(copyToClipboardButton).toBeVisible()
|
|
||||||
// Click the button.
|
|
||||||
await copyToClipboardButton.click()
|
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
|
||||||
const code2x4 = await page.locator('.cm-content').innerText()
|
|
||||||
await expect(code2x4.length).toBeGreaterThan(249)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
|
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -448,11 +413,16 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(
|
|
||||||
page,
|
page,
|
||||||
'alkjsdnlajshdbfjlhsbdf a;askjdnf'
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'alkjsdnlajshdbfjlhsbdf a;askjdnf',
|
||||||
|
cmdBar
|
||||||
)
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
@ -526,7 +496,9 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API')
|
const textToCadCommand = page.getByRole('option', {
|
||||||
|
name: 'Text-to-CAD Create',
|
||||||
|
})
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().scrollIntoViewIfNeeded()
|
await textToCadCommand.first().scrollIntoViewIfNeeded()
|
||||||
@ -544,96 +516,63 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendPromptFromCommandBarTriggeredByButton(
|
async function sendPromptFromCommandBarAndSetExistingProject(
|
||||||
page: Page,
|
page: Page,
|
||||||
promptStr: string
|
promptStr: string,
|
||||||
|
cmdBar: CmdBarFixture,
|
||||||
|
projectName = 'testDefault'
|
||||||
) {
|
) {
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
||||||
await page.getByTestId('text-to-cad').click()
|
await cmdBar.openCmdBar()
|
||||||
|
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
|
||||||
|
|
||||||
// Enter the prompt.
|
await cmdBar.expectState({
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
commandName: 'Text-to-CAD Create',
|
||||||
await expect(prompt.first()).toBeVisible()
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'method',
|
||||||
|
currentArgValue: '',
|
||||||
|
highlightedHeaderArg: 'method',
|
||||||
|
headerArguments: {
|
||||||
|
Method: '',
|
||||||
|
Prompt: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await cmdBar.currentArgumentInput.fill('existing')
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
// Type the prompt.
|
await cmdBar.expectState({
|
||||||
await page.keyboard.type(promptStr)
|
commandName: 'Text-to-CAD Create',
|
||||||
await page.waitForTimeout(200)
|
stage: 'arguments',
|
||||||
await page.keyboard.press('Enter')
|
currentArgKey: 'projectName',
|
||||||
|
currentArgValue: '',
|
||||||
|
highlightedHeaderArg: 'projectName',
|
||||||
|
headerArguments: {
|
||||||
|
Method: 'Existing project',
|
||||||
|
ProjectName: '',
|
||||||
|
Prompt: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await cmdBar.currentArgumentInput.fill(projectName)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Text-to-CAD Create',
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'prompt',
|
||||||
|
currentArgValue: '',
|
||||||
|
highlightedHeaderArg: 'prompt',
|
||||||
|
headerArguments: {
|
||||||
|
Method: 'Existing project',
|
||||||
|
ProjectName: projectName,
|
||||||
|
Prompt: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await cmdBar.currentArgumentInput.fill(promptStr)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
test(
|
|
||||||
'Text-to-CAD functionality',
|
|
||||||
{ tag: '@electron' },
|
|
||||||
async ({ context, page }, testInfo) => {
|
|
||||||
const projectName = 'project-000'
|
|
||||||
const prompt = 'lego 2x4'
|
|
||||||
const textToCadFileName = 'lego-2x4.kcl'
|
|
||||||
|
|
||||||
const { dir } = await context.folderSetupFn(async () => {})
|
|
||||||
|
|
||||||
const fileExists = () =>
|
|
||||||
fs.existsSync(join(dir, projectName, textToCadFileName))
|
|
||||||
|
|
||||||
const { openFilePanel, openKclCodePanel, waitForPageLoad } = await getUtils(
|
|
||||||
page,
|
|
||||||
test
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
// Locators
|
|
||||||
const projectMenuButton = page
|
|
||||||
.getByTestId('project-sidebar-toggle')
|
|
||||||
.filter({ hasText: projectName })
|
|
||||||
const textToCadFileButton = page.getByRole('listitem').filter({
|
|
||||||
has: page.getByRole('button', { name: textToCadFileName }),
|
|
||||||
})
|
|
||||||
const textToCadComment = page.getByText(
|
|
||||||
`// Generated by Text-to-CAD: ${prompt}`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create and navigate to the project
|
|
||||||
await createProject({ name: 'project-000', page })
|
|
||||||
|
|
||||||
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
|
|
||||||
await waitForPageLoad()
|
|
||||||
await openFilePanel()
|
|
||||||
await openKclCodePanel()
|
|
||||||
|
|
||||||
await test.step(`Test file creation`, async () => {
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, prompt)
|
|
||||||
// File is considered created if it shows up in the Project Files pane
|
|
||||||
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
|
|
||||||
expect(fileExists()).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Test file navigation`, async () => {
|
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
|
||||||
await textToCadFileButton.click()
|
|
||||||
// File can be navigated and loaded assuming a specific KCL comment is loaded into the KCL code pane
|
|
||||||
await expect(textToCadComment).toBeVisible({ timeout: 20_000 })
|
|
||||||
await expect(projectMenuButton).toContainText(textToCadFileName)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Test file deletion on rejection`, async () => {
|
|
||||||
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
|
||||||
// A file is created and can be navigated to while this prompt is still opened
|
|
||||||
// Click the "Reject" button within the prompt and it will delete the file.
|
|
||||||
await rejectButton.click()
|
|
||||||
|
|
||||||
const submittingToastMessage = page.getByText(
|
|
||||||
`Successfully deleted file "lego-2x4.kcl"`
|
|
||||||
)
|
|
||||||
await expect(submittingToastMessage).toBeVisible()
|
|
||||||
expect(fileExists()).toBeFalsy()
|
|
||||||
// Confirm we've navigated back to the main.kcl file after deletion
|
|
||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Below there are twelve (12) tests for testing the navigation and file creation
|
* Below there are twelve (12) tests for testing the navigation and file creation
|
||||||
* logic around text to cad. The Text to CAD command is now globally available
|
* logic around text to cad. The Text to CAD command is now globally available
|
||||||
@ -701,7 +640,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> New Project -> Stay in home page -> Reject -> Project should be deleted',
|
'Home Page -> Text To CAD -> New Project -> Stay in home page -> Reject -> Project should be deleted',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
const prompt = '2x2x2 cube'
|
const prompt = '2x2x2 cube'
|
||||||
@ -739,7 +678,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> New Project -> Stay in home page -> Accept -> should navigate to file',
|
'Home Page -> Text To CAD -> New Project -> Stay in home page -> Accept -> should navigate to file',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
@ -773,19 +712,19 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> Existing Project -> Stay in home page -> Reject -> should delete single file',
|
'Home Page -> Text To CAD -> Existing Project -> Stay in home page -> Reject -> should delete single file',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ homePage, page }, testInfo) => {
|
async ({ homePage, page }, testInfo) => {
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
const prompt = '2x2x2 cube'
|
const prompt = '2x2x2 cube'
|
||||||
@ -828,7 +767,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> Existing Project -> Stay in home page -> Accept -> should navigate to file',
|
'Home Page -> Text To CAD -> Existing Project -> Stay in home page -> Accept -> should navigate to file',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ homePage, page }, testInfo) => {
|
async ({ homePage, page }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
@ -867,19 +806,19 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> New Project -> Navigate to the project -> Reject -> should go to home page',
|
'Home Page -> Text To CAD -> New Project -> Navigate to the project -> Reject -> should go to home page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ homePage, page }, testInfo) => {
|
async ({ homePage, page }, testInfo) => {
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
const prompt = '2x2x2 cube'
|
const prompt = '2x2x2 cube'
|
||||||
@ -913,7 +852,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Reject' }).click()
|
await page.getByRole('button', { name: 'Reject' }).click()
|
||||||
@ -925,7 +864,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> New Project -> Navigate to the project -> Accept -> should stay in same file',
|
'Home Page -> Text To CAD -> New Project -> Navigate to the project -> Accept -> should stay in same file',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ homePage, page }, testInfo) => {
|
async ({ homePage, page }, testInfo) => {
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
const prompt = '2x2x2 cube'
|
const prompt = '2x2x2 cube'
|
||||||
@ -961,14 +900,14 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> Existing Project -> Navigate to the project -> Reject -> should load main.kcl',
|
'Home Page -> Text To CAD -> Existing Project -> Navigate to the project -> Reject -> should load main.kcl',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ homePage, page }, testInfo) => {
|
async ({ homePage, page }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
@ -1023,7 +962,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> Existing Project -> Navigate to the project -> Accept -> should load 2x2x2-cube.kcl',
|
'Home Page -> Text To CAD -> Existing Project -> Navigate to the project -> Accept -> should load 2x2x2-cube.kcl',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ homePage, page }, testInfo) => {
|
async ({ homePage, page }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
@ -1067,20 +1006,20 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check file is created
|
// Check file is created
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> New Project -> Navigate to different project -> Reject -> should stay in project',
|
'Home Page -> Text To CAD -> New Project -> Navigate to different project -> Reject -> should stay in project',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ homePage, page }, testInfo) => {
|
async ({ homePage, page }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
@ -1157,7 +1096,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> New Project -> Navigate to different project -> Accept -> should go to new project',
|
'Home Page -> Text To CAD -> New Project -> Navigate to different project -> Accept -> should go to new project',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, homePage }, testInfo) => {
|
async ({ page, homePage }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
@ -1213,24 +1152,20 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check file is created
|
// Check file is created
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('main.kcl')
|
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||||
).not.toBeVisible()
|
).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> Existing Project -> Navigate to different project -> Reject -> should stay in same project',
|
'Home Page -> Text To CAD -> Existing Project -> Navigate to different project -> Reject -> should stay in same project',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, homePage }, testInfo) => {
|
async ({ page, homePage }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
@ -1305,7 +1240,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Home Page -> Text To CAD -> Existing Project -> Navigate to different project -> Accept -> should navigate to new project',
|
'Home Page -> Text To CAD -> Existing Project -> Navigate to different project -> Accept -> should navigate to new project',
|
||||||
{ tag: '@electron' },
|
{ tag: '@desktop' },
|
||||||
async ({ page, homePage }, testInfo) => {
|
async ({ page, homePage }, testInfo) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const projectName = 'my-project-name'
|
const projectName = 'my-project-name'
|
||||||
@ -1363,13 +1298,13 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check file is created
|
// Check file is created
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('main.kcl')
|
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||||
|
@ -41,7 +41,7 @@ const playwrightTestFnWithFixtures_ = playwrightTestFn.extend<{
|
|||||||
}>({
|
}>({
|
||||||
tronApp: [
|
tronApp: [
|
||||||
async ({}, use, testInfo) => {
|
async ({}, use, testInfo) => {
|
||||||
if (process.env.PLATFORM === 'web') {
|
if (process.env.TARGET === 'web') {
|
||||||
await use(undefined)
|
await use(undefined)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|