Compare commits
173 Commits
kcl-test-s
...
stream-pau
Author | SHA1 | Date | |
---|---|---|---|
c6b80eec68 | |||
1b68f5dc19 | |||
a0aa4802d1 | |||
746f76ec63 | |||
8e624935c2 | |||
0a5f22c80a | |||
142db64796 | |||
59b0cdc3ac | |||
4763257dc3 | |||
b48ba7f081 | |||
64537a59b8 | |||
c24efaf2e4 | |||
2fb16ed074 | |||
82e647db3b | |||
fa2d0a69bf | |||
e372b2680e | |||
c1c1f817c9 | |||
00c0c993f2 | |||
b505c0be07 | |||
6c2d06c2c6 | |||
a4b7dd5182 | |||
0c2ca726d0 | |||
fcfecf702b | |||
347a6ef15a | |||
eed4386f76 | |||
14afcba599 | |||
faee6cbc64 | |||
0673e98fad | |||
b4eea5f842 | |||
2c9eb7f7c0 | |||
e259b2e3e8 | |||
91049204c5 | |||
0128c67aae | |||
ecc42b1e9c | |||
31811d0269 | |||
def5959836 | |||
4c7fab405b | |||
1e12e8d36b | |||
ad775891a3 | |||
efe207f4d2 | |||
01f0162991 | |||
2bbf7fad67 | |||
98549945a4 | |||
315fdc3060 | |||
c7e77e2597 | |||
a48679c014 | |||
c5e74866a9 | |||
731cb6c532 | |||
8a36a4c205 | |||
f29f2557de | |||
5f0ffb56c4 | |||
70078176b0 | |||
098fa2b5c9 | |||
2755156b84 | |||
a8b3ec660d | |||
3747c6ff0e | |||
bc1bc817ba | |||
b415e88746 | |||
9173e368a2 | |||
25928813e3 | |||
aec9cac7c7 | |||
0e82fbf7b0 | |||
f5975bbd61 | |||
765e587f6b | |||
6ccd5e22b8 | |||
c8bf82ba04 | |||
daad2039ec | |||
b567f6dfad | |||
41e85c77ac | |||
9f615b9d3e | |||
e8b5618b34 | |||
799b2d77b4 | |||
7b569f9b4f | |||
0f0c396a0c | |||
83214a88a3 | |||
dbab7876de | |||
6706695502 | |||
fa1f8d8d02 | |||
b4e59b5c56 | |||
20495383ac | |||
7f5fb83761 | |||
2ac874971e | |||
230e3132e9 | |||
bf9bb4fb22 | |||
31e7634669 | |||
84c71aa046 | |||
721b3e8cbd | |||
89309b6ccd | |||
15b163bba8 | |||
60d047ef6a | |||
f105044a47 | |||
9388e09c47 | |||
d71f2af9bd | |||
2bb372de12 | |||
0a3a8afbbd | |||
351df2f306 | |||
05a2eada9a | |||
788270d4fc | |||
6845f0c4bc | |||
563096fba4 | |||
35133c4f45 | |||
b78c6508c2 | |||
08b776134f | |||
cca544189c | |||
69754c82a2 | |||
afbee552ee | |||
b11772b27c | |||
6dc87aa4fe | |||
c147b3bfa2 | |||
7103ded32a | |||
81279aa4e8 | |||
550c8ae165 | |||
05610bb0f3 | |||
4a62862ca0 | |||
a4783d4951 | |||
30cfac06b8 | |||
c5509dabb1 | |||
239ab6850e | |||
4a7dd6e650 | |||
af2609e678 | |||
30909dedda | |||
39d76ed54f | |||
4925251c29 | |||
9772869545 | |||
a7e830cd02 | |||
ca102116b6 | |||
c2fba89e77 | |||
7e31678ba2 | |||
1140ced121 | |||
32b7ddaa7c | |||
2525f99515 | |||
4b8ce34b31 | |||
6617f72373 | |||
e9033e1754 | |||
9b697e30cf | |||
a70facdab4 | |||
4083f9f3dd | |||
7ead2bb875 | |||
19d01c563e | |||
dfe7cfc91c | |||
01443e445d | |||
e16eb49f51 | |||
5d5138e8e6 | |||
e1d6e29523 | |||
49657ad2e5 | |||
b40d353994 | |||
62ffa53add | |||
64dce4d8b1 | |||
02588b2672 | |||
3d1ac2ac0b | |||
ff5ce29fd7 | |||
4bd7e02271 | |||
26042790b6 | |||
af74f3bb05 | |||
0bdedf5854 | |||
d2c6b5cf3a | |||
c42967d0e7 | |||
cb8fc33adb | |||
2dc8b429ff | |||
19ffa220e8 | |||
5332ddd88e | |||
11d9a2ee00 | |||
bfebc41a5c | |||
824b4c823e | |||
785002fa4e | |||
f650281855 | |||
9f6999829a | |||
a14bbaa237 | |||
0706624381 | |||
ef0ae5e06e | |||
a010743abb | |||
057ee479c3 | |||
7218efc489 |
@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
|
||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock
|
||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts
|
||||
|
@ -4,9 +4,9 @@ set -euo pipefail
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
if [[ "$3" == "ubuntu-latest" ]]; then
|
||||
if [[ "$3" == ubuntu-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:ubuntu -- --shard=$1/$2 || true
|
||||
elif [[ "$3" == "windows-latest" ]]; then
|
||||
elif [[ "$3" == windows-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:windows -- --shard=$1/$2 || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
@ -26,9 +26,9 @@ while [[ $retry -le $max_retrys ]]; do
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
if [[ "$3" == "ubuntu-latest" ]]; then
|
||||
if [[ "$3" == ubuntu-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:ubuntu -- --last-failed || true
|
||||
elif [[ "$3" == "windows-latest" ]]; then
|
||||
elif [[ "$3" == windows-latest* ]]; then
|
||||
yarn test:playwright:browser:chrome:windows -- --last-failed || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
|
12
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -4,11 +4,11 @@ set -euo pipefail
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
if [[ "$1" == "ubuntu-latest" ]]; then
|
||||
if [[ "$1" == ubuntu-latest* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true
|
||||
elif [[ "$1" == "windows-latest" ]]; then
|
||||
elif [[ "$1" == windows-latest* ]]; then
|
||||
yarn test:playwright:electron:windows || true
|
||||
elif [[ "$1" == "macos-14" ]]; then
|
||||
elif [[ "$1" == macos-14* ]]; then
|
||||
yarn test:playwright:electron:macos || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
@ -28,11 +28,11 @@ while [[ $retry -le $max_retrys ]]; do
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
if [[ "$1" == "ubuntu-latest" ]]; then
|
||||
if [[ "$1" == ubuntu-latest* ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true
|
||||
elif [[ "$1" == "windows-latest" ]]; then
|
||||
elif [[ "$1" == windows-latest* ]]; then
|
||||
yarn test:playwright:electron:windows -- --last-failed || true
|
||||
elif [[ "$1" == "macos-14" ]]; then
|
||||
elif [[ "$1" == macos-14* ]]; then
|
||||
yarn test:playwright:electron:macos -- --last-failed || true
|
||||
else
|
||||
echo "Do not run playwright. Unable to detect os runtime."
|
||||
|
6
.github/dependabot.yml
vendored
@ -8,21 +8,21 @@ updates:
|
||||
- package-ecosystem: 'npm' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: 'weekly'
|
||||
reviewers:
|
||||
- franknoirot
|
||||
- irev-dev
|
||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: 'weekly'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src/wasm-lib/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: 'weekly'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
|
65
.github/workflows/build-test-publish-apps.yml
vendored
@ -15,6 +15,7 @@ on:
|
||||
env:
|
||||
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@ -25,7 +26,6 @@ jobs:
|
||||
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||
outputs:
|
||||
version: ${{ steps.export_version.outputs.version }}
|
||||
notes: ${{ steps.export_version.outputs.notes }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -55,8 +55,6 @@ jobs:
|
||||
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
|
||||
|
||||
- name: Generate release notes
|
||||
env:
|
||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
||||
run: |
|
||||
echo "$NOTES" > release-notes.md
|
||||
cat release-notes.md
|
||||
@ -84,13 +82,10 @@ jobs:
|
||||
path: |
|
||||
electron-builder.yml
|
||||
|
||||
- id: export_notes
|
||||
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Prepare electron-builder.yml file for updater test
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test-release-notes"' electron-builder.yml
|
||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test"' electron-builder.yml
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
@ -114,17 +109,8 @@ jobs:
|
||||
platform: linux
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
||||
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -186,8 +172,26 @@ jobs:
|
||||
smksp_cert_sync.exe
|
||||
shell: cmd
|
||||
|
||||
- name: Build the app
|
||||
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
|
||||
- name: Build the app (debug)
|
||||
if: ${{ env.BUILD_RELEASE == 'false' }}
|
||||
# electron-builder doesn't have a concept of release vs debug,
|
||||
# this is just not doing any codesign or release yml generation
|
||||
run: yarn electron-builder --config
|
||||
|
||||
- name: Build the app (release)
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
env:
|
||||
PUBLISH_FOR_PULL_REQUEST: true
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||
run: yarn electron-builder --config --publish always
|
||||
|
||||
- name: List artifacts in out/
|
||||
run: ls -R out
|
||||
@ -211,7 +215,7 @@ jobs:
|
||||
out/*-x86_64-linux.*
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
with:
|
||||
name: out-yml
|
||||
path: |
|
||||
@ -231,7 +235,17 @@ jobs:
|
||||
|
||||
- name: Build the app (updater-test)
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||
run: yarn electron-builder --config --publish always
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
@ -262,7 +276,6 @@ jobs:
|
||||
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
||||
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
||||
NOTES: ${{ needs.prepare-files.outputs.notes }}
|
||||
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
|
||||
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
|
||||
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
||||
@ -348,17 +361,17 @@ jobs:
|
||||
run: "ls -R out"
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
uses: 'google-github-actions/auth@v2.1.6'
|
||||
uses: 'google-github-actions/auth@v2.1.7'
|
||||
with:
|
||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||
|
||||
- name: Set up Google Cloud SDK
|
||||
uses: google-github-actions/setup-gcloud@v2.1.0
|
||||
uses: google-github-actions/setup-gcloud@v2.1.2
|
||||
with:
|
||||
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
|
||||
|
||||
- name: Upload release files to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.1
|
||||
with:
|
||||
path: out
|
||||
glob: 'Zoo*'
|
||||
@ -366,7 +379,7 @@ jobs:
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload update endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.1
|
||||
with:
|
||||
path: out
|
||||
glob: 'latest*'
|
||||
@ -374,7 +387,7 @@ jobs:
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload download endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.1
|
||||
with:
|
||||
path: last_download.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
2
.github/workflows/cargo-check.yml
vendored
@ -37,4 +37,4 @@ jobs:
|
||||
# We specifically want to test the disable-println feature
|
||||
# Since it is not enabled by default, we need to specify it
|
||||
# This is used in kcl-lsp
|
||||
cargo check --all --features disable-println --features pyo3 --features cli
|
||||
cargo check --workspace --features disable-println --features pyo3 --features cli
|
||||
|
6
.github/workflows/cargo-test.yml
vendored
@ -5,6 +5,8 @@ on:
|
||||
paths:
|
||||
- 'src/wasm-lib/**.rs'
|
||||
- 'src/wasm-lib/**.hbs'
|
||||
- 'src/wasm-lib/**.gen'
|
||||
- 'src/wasm-lib/**.snap'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
@ -15,6 +17,8 @@ on:
|
||||
paths:
|
||||
- 'src/wasm-lib/**.rs'
|
||||
- 'src/wasm-lib/**.hbs'
|
||||
- 'src/wasm-lib/**.gen'
|
||||
- 'src/wasm-lib/**.snap'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
@ -62,7 +66,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |-
|
||||
cd "${{ matrix.dir }}"
|
||||
cargo llvm-cov nextest --all --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
|
||||
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
|
||||
env:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||
RUST_MIN_STACK: 10485760000
|
||||
|
6
.github/workflows/e2e-tests.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores]
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
runs-on: ${{ matrix.os }}
|
||||
@ -227,7 +227,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-14]
|
||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large]
|
||||
timeout-minutes: 60
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: check-rust-changes
|
||||
@ -287,7 +287,7 @@ jobs:
|
||||
brew install gnu-sed
|
||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Install vector
|
||||
if: ${{ !startsWith(matrix.os, 'windows') }}
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu') }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
|
25
.github/workflows/static-analysis.yml
vendored
@ -81,6 +81,31 @@ jobs:
|
||||
- name: Run codespell
|
||||
run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration.
|
||||
|
||||
yarn-unit-test-kcl-samples:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:bg
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
|
||||
- name: Install Chromium Browser
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn playwright install chromium --with-deps
|
||||
|
||||
- name: run unit tests for kcl samples
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn test:unit:kcl-samples
|
||||
env:
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
|
||||
yarn-unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
2
Makefile
@ -19,7 +19,7 @@ $(XSTATE_TYPEGENS): $(TS_SRC)
|
||||
yarn xstate typegen 'src/**/*.ts?(x)'
|
||||
|
||||
public/wasm_lib_bg.wasm: $(WASM_LIB_FILES)
|
||||
yarn build:wasm-dev
|
||||
yarn build:wasm
|
||||
|
||||
node_modules: package.json yarn.lock
|
||||
yarn install
|
||||
|
@ -110,7 +110,7 @@ Which commands from setup are one off vs need to be run every time?
|
||||
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
||||
```bash
|
||||
yarn install
|
||||
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
|
||||
yarn build:wasm
|
||||
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
||||
```
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: "angleToMatchLengthX"
|
||||
excerpt: "Compute the angle (in degrees) in o"
|
||||
excerpt: "Returns the angle to match the given length for x."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Compute the angle (in degrees) in o
|
||||
Returns the angle to match the given length for x.
|
||||
|
||||
|
||||
|
||||
|
@ -74,17 +74,23 @@ layout: manual
|
||||
* [`patternTransform`](kcl/patternTransform)
|
||||
* [`pi`](kcl/pi)
|
||||
* [`polar`](kcl/polar)
|
||||
* [`polygon`](kcl/polygon)
|
||||
* [`pow`](kcl/pow)
|
||||
* [`profileStart`](kcl/profileStart)
|
||||
* [`profileStartX`](kcl/profileStartX)
|
||||
* [`profileStartY`](kcl/profileStartY)
|
||||
* [`push`](kcl/push)
|
||||
* [`reduce`](kcl/reduce)
|
||||
* [`rem`](kcl/rem)
|
||||
* [`revolve`](kcl/revolve)
|
||||
* [`segAng`](kcl/segAng)
|
||||
* [`segEnd`](kcl/segEnd)
|
||||
* [`segEndX`](kcl/segEndX)
|
||||
* [`segEndY`](kcl/segEndY)
|
||||
* [`segLen`](kcl/segLen)
|
||||
* [`segStart`](kcl/segStart)
|
||||
* [`segStartX`](kcl/segStartX)
|
||||
* [`segStartY`](kcl/segStartY)
|
||||
* [`shell`](kcl/shell)
|
||||
* [`sin`](kcl/sin)
|
||||
* [`sqrt`](kcl/sqrt)
|
||||
|
59
docs/kcl/modules.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "KCL Modules"
|
||||
excerpt: "Documentation of modules for the KCL language for the Zoo Modeling App."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
`KCL` allows splitting code up into multiple files. Each file is somewhat
|
||||
isolated from other files as a separate module.
|
||||
|
||||
When you define a function, you can use `export` before it to make it available
|
||||
to other modules.
|
||||
|
||||
```
|
||||
// util.kcl
|
||||
export fn increment = (x) => {
|
||||
return x + 1
|
||||
}
|
||||
```
|
||||
|
||||
Other files in the project can now import functions that have been exported.
|
||||
This makes them available to use in another file.
|
||||
|
||||
```
|
||||
// main.kcl
|
||||
import increment from "util.kcl"
|
||||
|
||||
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
|
||||
an `import` statement inside a function or in the body of an if-else.
|
||||
|
||||
Multiple functions can be exported in a file.
|
||||
|
||||
```
|
||||
// util.kcl
|
||||
export fn increment = (x) => {
|
||||
return x + 1
|
||||
}
|
||||
|
||||
export fn decrement = (x) => {
|
||||
return x - 1
|
||||
}
|
||||
```
|
||||
|
||||
When importing, you can import multiple functions at once.
|
||||
|
||||
```
|
||||
import increment, decrement from "util.kcl"
|
||||
```
|
||||
|
||||
Imported symbols can be renamed for convenience or to avoid name collisions.
|
||||
|
||||
```
|
||||
import increment as inc, decrement as dec from "util.kcl"
|
||||
```
|
60
docs/kcl/polygon.md
Normal file
38
docs/kcl/push.md
Normal file
53
docs/kcl/segEnd.md
Normal file
56
docs/kcl/segStart.md
Normal file
43
docs/kcl/segStartX.md
Normal file
44
docs/kcl/segStartY.md
Normal file
77823
docs/kcl/std.json
@ -23,11 +23,11 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Literal`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
|
||||
| `raw` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -43,10 +43,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -62,12 +62,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `BinaryExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| | No |
|
||||
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
|
||||
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -83,12 +83,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `CallExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
||||
| `optional` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -104,11 +104,11 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `UnaryExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| | No |
|
||||
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -124,12 +124,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `MemberExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
|
||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
|
||||
| `computed` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -145,13 +145,13 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `IfExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
|
||||
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
|
||||
| `final_else` |[`Program`](/docs/kcl/types/Program)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -23,12 +23,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ImportStatement`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
|
||||
| `path` |`string`| | No |
|
||||
| `raw_path` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -44,10 +44,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ExpressionStatement`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `expression` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -63,12 +63,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `VariableDeclaration`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | No |
|
||||
| `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)| | No |
|
||||
| `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -84,10 +84,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ReturnStatement`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `argument` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -15,10 +15,10 @@ layout: manual
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -16,6 +16,6 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `bindings` |`object`| | No |
|
||||
| `parent` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `parent` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -24,11 +24,11 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Literal`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `raw` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -44,10 +44,10 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -63,10 +63,10 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -82,12 +82,12 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `BinaryExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -103,11 +103,11 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
|
||||
| `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -123,12 +123,12 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `CallExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
||||
| `optional` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -144,11 +144,11 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `PipeExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -164,9 +164,9 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `PipeSubstitution`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -182,11 +182,11 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ArrayExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -202,12 +202,12 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ArrayRangeExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -223,11 +223,11 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ObjectExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -243,12 +243,12 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `MemberExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `computed` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -264,11 +264,11 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `UnaryExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -284,13 +284,13 @@ An expression can be evaluated to yield a single KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `IfExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
|
||||
| `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -307,8 +307,8 @@ KCL value for an optional parameter which was not given an argument. (remember,
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `None`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -15,10 +15,10 @@ layout: manual
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
|
||||
| `body` |[`Program`](/docs/kcl/types/Program)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -15,9 +15,9 @@ layout: manual
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -17,8 +17,8 @@ layout: manual
|
||||
|----------|------|-------------|----------|
|
||||
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No |
|
||||
| `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
16
docs/kcl/types/KclNone.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "KclNone"
|
||||
excerpt: "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application)."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -23,8 +23,110 @@ Any KCL value.
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `UserVal`| | No |
|
||||
| `value` |``| | No |
|
||||
| `type` |enum: `Uuid`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Bool`| | No |
|
||||
| `value` |`boolean`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Number`| | No |
|
||||
| `value` |`number`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Int`| | No |
|
||||
| `value` |`integer`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `String`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Array`| | No |
|
||||
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Object`| | No |
|
||||
| `value` |`object`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
@ -59,10 +161,10 @@ Any KCL value.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -111,6 +213,38 @@ A face.
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Sketch`](/docs/kcl/types/Sketch)| | No |
|
||||
| `value` |[`Sketch`](/docs/kcl/types/Sketch)| Any KCL value. | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Sketches`| | No |
|
||||
| `value` |`[` [`Sketch`](/docs/kcl/types/Sketch) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
An solid is a collection of extrude surfaces.
|
||||
|
||||
@ -190,6 +324,23 @@ Data for an imported geometry.
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No |
|
||||
| `value` |[`KclNone`](/docs/kcl/types/KclNone)| Any KCL value. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -23,10 +23,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -42,11 +42,11 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Literal`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
|
||||
| `raw` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -23,12 +23,12 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `MemberExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
|
||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
|
||||
| `computed` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -44,10 +44,10 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -16,7 +16,7 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `nonCodeNodes` |`object`| | No |
|
||||
| `start` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | No |
|
||||
| `startNodes` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
|
||||
|
||||
|
@ -15,9 +15,9 @@ layout: manual
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -15,10 +15,10 @@ layout: manual
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `key` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `value` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -162,6 +162,28 @@ A base path.
|
||||
|
||||
|
||||
----
|
||||
A circular arc, not necessarily tangential to the current point.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Arc`| | No |
|
||||
| `center` |`[number, number]`| Center of the circle that this arc is drawn on. | No |
|
||||
| `radius` |`number`| Radius of the circle that this arc is drawn on. | No |
|
||||
| `from` |`[number, number]`| The from point. | No |
|
||||
| `to` |`[number, number]`| The to point. | No |
|
||||
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
|
||||
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
24
docs/kcl/types/PolygonData.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "PolygonData"
|
||||
excerpt: "Data for drawing a polygon"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Data for drawing a polygon
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `radius` |`number`| The radius of the polygon | No |
|
||||
| `numSides` |`integer`| The number of sides in the polygon | No |
|
||||
| `center` |`[number, number]`| The center point of the polygon | No |
|
||||
| `inscribed` |`boolean`| Whether the polygon is inscribed (true) or circumscribed (false) about a circle with the specified radius | No |
|
||||
|
||||
|
@ -16,10 +16,10 @@ A KCL program top level, or function body.
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ layout: manual
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
|
||||
| `currentEnv` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `currentEnv` |`integer`| | No |
|
||||
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |
|
||||
|
||||
|
||||
|
@ -16,8 +16,8 @@ A sketch is a collection of paths.
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
|
||||
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
|
||||
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||
|
@ -25,8 +25,8 @@ A sketch is a collection of paths.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `sketch`| | No |
|
||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
|
||||
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
|
||||
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||
|
@ -18,7 +18,7 @@ Engine information for a tag.
|
||||
|----------|------|-------------|----------|
|
||||
| `id` |`string`| The id of the tagged object. | No |
|
||||
| `sketch` |`string`| The sketch the tag is on. | No |
|
||||
| `path` |[`BasePath`](/docs/kcl/types/BasePath)| The path the tag is on. | No |
|
||||
| `path` |[`Path`](/docs/kcl/types/Path)| The path the tag is on. | No |
|
||||
| `surface` |[`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface)| The surface information for the tag. | No |
|
||||
|
||||
|
||||
|
@ -15,10 +15,10 @@ layout: manual
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No |
|
||||
| `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
|
@ -67,15 +67,15 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
}
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
} else {
|
||||
await page.waitForTimeout(500)
|
||||
}
|
||||
@ -84,9 +84,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
||||
|> line([-${commonPoints.num2}, 0], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
}
|
||||
|
||||
// deselect line tool
|
||||
@ -142,9 +142,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %, $seg01)
|
||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
||||
|> angledLine([180, segLen(seg01)], %)`)
|
||||
|> xLine(${commonPoints.num1}, %, $seg01)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(-segLen(seg01), %)`)
|
||||
}
|
||||
|
||||
test.describe('Basic sketch', () => {
|
||||
|
@ -313,3 +313,45 @@ test(
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'external change of file contents are reflected in editor',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const PROJECT_DIR_NAME = 'lee-was-here'
|
||||
const {
|
||||
electronApp,
|
||||
page,
|
||||
dir: projectsDir,
|
||||
} = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const aProjectDir = join(dir, PROJECT_DIR_NAME)
|
||||
await fsp.mkdir(aProjectDir, { recursive: true })
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await test.step('Open the project', async () => {
|
||||
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
|
||||
await page.getByText(PROJECT_DIR_NAME).click()
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
await u.openFilePanel()
|
||||
await u.openKclCodePanel()
|
||||
|
||||
await test.step('Write to file externally and check for changed content', async () => {
|
||||
const content = 'ha he ho ho ha blap scap be dap'
|
||||
await fsp.writeFile(
|
||||
join(projectsDir, PROJECT_DIR_NAME, 'main.kcl'),
|
||||
content
|
||||
)
|
||||
await u.editorTextMatches(content)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
@ -104,7 +104,7 @@ test(
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBe(431341)
|
||||
.toBeGreaterThan(300_000)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
@ -179,7 +179,7 @@ test(
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBe(102040)
|
||||
.toBeGreaterThan(100_000)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
|
@ -1,6 +1,16 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import fsp from 'fs/promises'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import {
|
||||
darkModeBgColor,
|
||||
darkModePlaneColorXZ,
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
import { join } from 'path'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
@ -622,16 +632,18 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// this test might be brittle as we add and remove functions
|
||||
// but should also be easy to update.
|
||||
// tests clicking on an option, selection the first option
|
||||
// and arrowing down to an option
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('sketch001 = start')
|
||||
|
||||
// expect there to be six auto complete options
|
||||
await expect(page.locator('.cm-completionLabel')).toHaveCount(8)
|
||||
// expect there to be some auto complete options
|
||||
// exact number depends on the KCL stdlib, so let's just check it's > 0 for now.
|
||||
await expect(async () => {
|
||||
const children = await page.locator('.cm-completionLabel').count()
|
||||
expect(children).toBeGreaterThan(0)
|
||||
}).toPass()
|
||||
// this makes sure we can accept a completion with click
|
||||
await page.getByText('startSketchOn').click()
|
||||
await page.keyboard.type("'XZ'")
|
||||
@ -682,6 +694,9 @@ test.describe('Editor tests', () => {
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.14, 12], %)
|
||||
|> xLine(5, %) // lin`)
|
||||
|
||||
// expect there to be no KCL errors
|
||||
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('with tab to accept the completion', async ({ page }) => {
|
||||
@ -974,4 +989,84 @@ test.describe('Editor tests', () => {
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
})
|
||||
|
||||
test.fixme(
|
||||
`Can use the import stdlib function on a local OBJ file`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'cube')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.obj'),
|
||||
join(bracketDir, 'cube.obj')
|
||||
)
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
|
||||
},
|
||||
})
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
// Locators and constants
|
||||
const u = await getUtils(page)
|
||||
const projectLink = page.getByRole('link', { name: 'cube' })
|
||||
const gizmo = page.locator('[aria-label*=gizmo]')
|
||||
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
|
||||
const locationToHavColor = async (
|
||||
position: { x: number; y: number },
|
||||
color: [number, number, number]
|
||||
) => {
|
||||
return u.getGreatestPixDiff(position, color)
|
||||
}
|
||||
const notTheOrigin = {
|
||||
x: viewportSize.width * 0.55,
|
||||
y: viewportSize.height * 0.3,
|
||||
}
|
||||
const origin = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||
const errorIndicators = page.locator('.cm-lint-marker-error')
|
||||
|
||||
await test.step(`Open the empty file, see the default planes`, async () => {
|
||||
await projectLink.click()
|
||||
await u.waitForPageLoad()
|
||||
await expect
|
||||
.poll(
|
||||
async () => locationToHavColor(notTheOrigin, darkModePlaneColorXZ),
|
||||
{
|
||||
timeout: 5000,
|
||||
message: 'XZ plane color is visible',
|
||||
}
|
||||
)
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
await test.step(`Write the import function line`, async () => {
|
||||
await u.codeLocator.fill(`import('cube.obj')`)
|
||||
await page.waitForTimeout(800)
|
||||
})
|
||||
await test.step(`Reset the camera before checking`, async () => {
|
||||
await u.doAndWaitForCmd(async () => {
|
||||
await gizmo.click({ button: 'right' })
|
||||
await resetCameraButton.click()
|
||||
}, 'zoom_to_fit')
|
||||
})
|
||||
await test.step(`Verify that we see the imported geometry and no errors`, async () => {
|
||||
await expect(errorIndicators).toHaveCount(0)
|
||||
await expect
|
||||
.poll(async () => locationToHavColor(origin, darkModePlaneColorXZ), {
|
||||
timeout: 3000,
|
||||
message: 'Plane color should not be visible',
|
||||
})
|
||||
.toBeGreaterThan(15)
|
||||
await expect
|
||||
.poll(async () => locationToHavColor(origin, darkModeBgColor), {
|
||||
timeout: 3000,
|
||||
message: 'Background color should not be visible',
|
||||
})
|
||||
.toBeGreaterThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -3,6 +3,7 @@ import { test, expect } from './fixtures/fixtureSetup'
|
||||
import * as fsp from 'fs/promises'
|
||||
import * as fs from 'fs'
|
||||
import {
|
||||
createProject,
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
setup,
|
||||
@ -25,10 +26,6 @@ test.describe('integrations tests', () => {
|
||||
'Creating a new file or switching file while in sketchMode should exit sketchMode',
|
||||
{ tag: '@electron' },
|
||||
async ({ tronApp, homePage, scene, editor, toolbar }) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'windows times out will waiting for the execution indicator?'
|
||||
)
|
||||
await tronApp.initialise({
|
||||
fixtures: { homePage, scene, editor, toolbar },
|
||||
folderSetupFn: async (dir) => {
|
||||
@ -54,7 +51,6 @@ test.describe('integrations tests', () => {
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.openProject('test-sample')
|
||||
// windows times out here, hence the skip above
|
||||
await scene.waitForExecutionDone()
|
||||
})
|
||||
await test.step('enter sketch mode', async () => {
|
||||
@ -70,10 +66,13 @@ test.describe('integrations tests', () => {
|
||||
await toolbar.editSketch()
|
||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||
})
|
||||
|
||||
const fileName = 'Untitled.kcl'
|
||||
await test.step('check sketch mode is exited when creating new file', async () => {
|
||||
await toolbar.fileTreeBtn.click()
|
||||
await toolbar.expectFileTreeState(['main.kcl'])
|
||||
await toolbar.createFile({ wait: true })
|
||||
|
||||
await toolbar.createFile({ fileName, waitForToastToDisappear: true })
|
||||
|
||||
// check we're out of sketch mode
|
||||
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||
@ -92,10 +91,10 @@ test.describe('integrations tests', () => {
|
||||
})
|
||||
await toolbar.editSketch()
|
||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||
await toolbar.expectFileTreeState(['main.kcl', 'Untitled.kcl'])
|
||||
await toolbar.expectFileTreeState(['main.kcl', fileName])
|
||||
})
|
||||
await test.step('check sketch mode is exited when opening a different file', async () => {
|
||||
await toolbar.openFile('untitled.kcl', { wait: false })
|
||||
await toolbar.openFile(fileName, { wait: false })
|
||||
|
||||
// check we're out of sketch mode
|
||||
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||
@ -108,26 +107,21 @@ test.describe('when using the file tree to', () => {
|
||||
const fromFile = 'main.kcl'
|
||||
const toFile = 'hello.kcl'
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
renameFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(tronApp.page, test)
|
||||
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } =
|
||||
await getUtils(tronApp.page, test)
|
||||
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
await createProject({ name: 'project-000', page: tronApp.page })
|
||||
|
||||
// File the main.kcl with contents
|
||||
const kclCube = await fsp.readFile(
|
||||
@ -136,6 +130,9 @@ test.describe('when using the file tree to', () => {
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
|
||||
await tronApp.page.waitForTimeout(2000)
|
||||
|
||||
await renameFile(fromFile, toFile)
|
||||
await tronApp.page.reload()
|
||||
|
||||
@ -158,21 +155,20 @@ test.describe('when using the file tree to', () => {
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
`create many new untitled files they increment their names`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
|
||||
const { panesOpen, createAndSelectProject, createNewFile } =
|
||||
await getUtils(tronApp.page, test)
|
||||
const { panesOpen, createNewFile } = await getUtils(tronApp.page, test)
|
||||
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
await createProject({ name: 'project-000', page: tronApp.page })
|
||||
|
||||
await createNewFile('')
|
||||
await createNewFile('')
|
||||
@ -195,57 +191,74 @@ test.describe('when using the file tree to', () => {
|
||||
test(
|
||||
'create a new file with the same name as an existing file cancels the operation',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
async (
|
||||
{ browser: _, tronApp, homePage, scene, editor, toolbar },
|
||||
testInfo
|
||||
) => {
|
||||
const projectName = 'cube'
|
||||
const mainFile = 'main.kcl'
|
||||
const secondFile = 'cylinder.kcl'
|
||||
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8')
|
||||
const kclCylinder = await fsp.readFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
'utf-8'
|
||||
)
|
||||
await tronApp.initialise({
|
||||
fixtures: { homePage, scene, editor, toolbar },
|
||||
folderSetupFn: async (dir) => {
|
||||
const cubeDir = join(dir, projectName)
|
||||
await fsp.mkdir(cubeDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.kcl'),
|
||||
join(cubeDir, mainFile)
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(cubeDir, secondFile)
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
openKclCodePanel,
|
||||
openFilePanel,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
createNewFileAndSelect,
|
||||
renameFile,
|
||||
selectFile,
|
||||
editorTextMatches,
|
||||
waitForPageLoad,
|
||||
} = await getUtils(tronApp.page, _test)
|
||||
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
await test.step(`Setup: Open project and navigate to ${secondFile}`, async () => {
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: projectName,
|
||||
fileCount: 2,
|
||||
folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.openProject(projectName)
|
||||
await waitForPageLoad()
|
||||
await openFilePanel()
|
||||
await selectFile(secondFile)
|
||||
})
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
await openKclCodePanel()
|
||||
await openFilePanel()
|
||||
// File the main.kcl with contents
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
await test.step(`Attempt to rename ${secondFile} to ${mainFile}`, async () => {
|
||||
await renameFile(secondFile, mainFile)
|
||||
})
|
||||
|
||||
const kcl1 = 'main.kcl'
|
||||
const kcl2 = '2.kcl'
|
||||
|
||||
await createNewFileAndSelect(kcl2)
|
||||
const kclCylinder = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCylinder)
|
||||
|
||||
await renameFile(kcl2, kcl1)
|
||||
|
||||
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {
|
||||
await selectFile(kcl1)
|
||||
await test.step(`Postcondition: ${mainFile} still has the original content`, async () => {
|
||||
await selectFile(mainFile)
|
||||
await editorTextMatches(kclCube)
|
||||
})
|
||||
await tronApp.page.waitForTimeout(500)
|
||||
|
||||
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
|
||||
await selectFile(kcl2)
|
||||
await test.step(`Postcondition: ${secondFile} still exists with the original content`, async () => {
|
||||
await selectFile(secondFile)
|
||||
await editorTextMatches(kclCylinder)
|
||||
})
|
||||
|
||||
await tronApp?.close?.()
|
||||
await tronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
@ -255,20 +268,15 @@ test.describe('when using the file tree to', () => {
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
deleteFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(tronApp.page, _test)
|
||||
const { panesOpen, pasteCodeInEditor, deleteFile, editorTextMatches } =
|
||||
await getUtils(tronApp.page, _test)
|
||||
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
await createProject({ name: 'project-000', page: tronApp.page })
|
||||
// File the main.kcl with contents
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
@ -276,11 +284,11 @@ test.describe('when using the file tree to', () => {
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
const kcl1 = 'main.kcl'
|
||||
const mainFile = 'main.kcl'
|
||||
|
||||
await deleteFile(kcl1)
|
||||
await deleteFile(mainFile)
|
||||
|
||||
await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => {
|
||||
await test.step(`Postcondition: ${mainFile} is recreated but has no content`, async () => {
|
||||
await editorTextMatches('')
|
||||
})
|
||||
|
||||
@ -288,7 +296,7 @@ test.describe('when using the file tree to', () => {
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
'loading small file, then large, then back to small',
|
||||
{
|
||||
tag: '@electron',
|
||||
@ -298,7 +306,6 @@ test.describe('when using the file tree to', () => {
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
createNewFile,
|
||||
openDebugPanel,
|
||||
@ -310,7 +317,7 @@ test.describe('when using the file tree to', () => {
|
||||
tronApp.page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
await createAndSelectProject('project-000')
|
||||
await createProject({ name: 'project-000', page: tronApp.page })
|
||||
|
||||
// Create a small file
|
||||
const kclCube = await fsp.readFile(
|
||||
@ -714,7 +721,7 @@ _test.describe('Renaming in the file tree', () => {
|
||||
})
|
||||
|
||||
await _test.step('Rename the folder', async () => {
|
||||
await page.waitForTimeout(60000)
|
||||
await page.waitForTimeout(1000)
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await _expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
@ -960,4 +967,171 @@ _test.describe('Deleting items from the file pane', () => {
|
||||
'TODO - delete folder we are in, with no main.kcl',
|
||||
async () => {}
|
||||
)
|
||||
|
||||
// Copied from tests above.
|
||||
_test(
|
||||
`external deletion of project navigates back home`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const TEST_PROJECT_NAME = 'Test Project'
|
||||
const {
|
||||
electronApp,
|
||||
page,
|
||||
dir: projectsDirName,
|
||||
} = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, TEST_PROJECT_NAME), { recursive: true })
|
||||
await fsp.mkdir(join(dir, TEST_PROJECT_NAME, 'folderToDelete'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, TEST_PROJECT_NAME, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, TEST_PROJECT_NAME, 'folderToDelete', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText(TEST_PROJECT_NAME)
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToDelete = page.getByRole('button', {
|
||||
name: 'folderToDelete',
|
||||
})
|
||||
const fileWithinFolder = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||
})
|
||||
|
||||
await _test.step(
|
||||
'Open project and navigate into folderToDelete',
|
||||
async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
await u.closeKclCodePanel()
|
||||
await u.openFilePanel()
|
||||
|
||||
await folderToDelete.click()
|
||||
await _expect(fileWithinFolder).toBeVisible()
|
||||
await fileWithinFolder.click()
|
||||
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
}
|
||||
)
|
||||
|
||||
// Point of divergence. Delete the project folder and see if it goes back
|
||||
// to the home view.
|
||||
await _test.step(
|
||||
'Delete projectsDirName/<project-name> externally',
|
||||
async () => {
|
||||
await fsp.rm(join(projectsDirName, TEST_PROJECT_NAME), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
await _test.step('Check the app is back on the home view', async () => {
|
||||
const projectsDirLink = page.getByText('Loaded from')
|
||||
await _expect(projectsDirLink).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
// Similar to the above
|
||||
_test(
|
||||
`external deletion of file in sub-directory updates the file tree and recreates it on code editor typing`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const TEST_PROJECT_NAME = 'Test Project'
|
||||
const {
|
||||
electronApp,
|
||||
page,
|
||||
dir: projectsDirName,
|
||||
} = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, TEST_PROJECT_NAME), { recursive: true })
|
||||
await fsp.mkdir(join(dir, TEST_PROJECT_NAME, 'folderToDelete'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, TEST_PROJECT_NAME, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, TEST_PROJECT_NAME, 'folderToDelete', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText(TEST_PROJECT_NAME)
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToDelete = page.getByRole('button', {
|
||||
name: 'folderToDelete',
|
||||
})
|
||||
const fileWithinFolder = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||
})
|
||||
|
||||
await _test.step(
|
||||
'Open project and navigate into folderToDelete',
|
||||
async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
await u.openFilePanel()
|
||||
|
||||
await folderToDelete.click()
|
||||
await _expect(fileWithinFolder).toBeVisible()
|
||||
await fileWithinFolder.click()
|
||||
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
}
|
||||
)
|
||||
|
||||
await _test.step(
|
||||
'Delete projectsDirName/<project-name> externally',
|
||||
async () => {
|
||||
await fsp.rm(
|
||||
join(
|
||||
projectsDirName,
|
||||
TEST_PROJECT_NAME,
|
||||
'folderToDelete',
|
||||
'someFileWithin.kcl'
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
await _test.step('Check the file is gone in the file tree', async () => {
|
||||
await _expect(
|
||||
page.getByTestId('file-pane-scroll-container')
|
||||
).not.toContainText('someFileWithin.kcl')
|
||||
})
|
||||
|
||||
await _test.step(
|
||||
'Check the file is back in the file tree after typing in code editor',
|
||||
async () => {
|
||||
await u.pasteCodeInEditor('hello = 1')
|
||||
await _expect(
|
||||
page.getByTestId('file-pane-scroll-container')
|
||||
).toContainText('someFileWithin.kcl')
|
||||
}
|
||||
)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -1,6 +1,11 @@
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
import { sansWhitespace } from '../test-utils'
|
||||
import {
|
||||
closePane,
|
||||
checkIfPaneIsOpen,
|
||||
openPane,
|
||||
sansWhitespace,
|
||||
} from '../test-utils'
|
||||
|
||||
interface EditorState {
|
||||
activeLines: Array<string>
|
||||
@ -11,6 +16,7 @@ interface EditorState {
|
||||
export class EditorFixture {
|
||||
public page: Page
|
||||
|
||||
private paneButtonTestId = 'code-pane-button'
|
||||
private diagnosticsTooltip!: Locator
|
||||
private diagnosticsGutterIcon!: Locator
|
||||
private codeContent!: Locator
|
||||
@ -31,19 +37,32 @@ export class EditorFixture {
|
||||
|
||||
private _expectEditorToContain =
|
||||
(not = false) =>
|
||||
(
|
||||
async (
|
||||
code: string,
|
||||
{
|
||||
shouldNormalise = false,
|
||||
timeout = 5_000,
|
||||
}: { shouldNormalise?: boolean; timeout?: number } = {}
|
||||
) => {
|
||||
const wasPaneOpen = await this.checkIfPaneIsOpen()
|
||||
if (!wasPaneOpen) {
|
||||
await this.openPane()
|
||||
}
|
||||
const resetPane = async () => {
|
||||
if (!wasPaneOpen) {
|
||||
await this.closePane()
|
||||
}
|
||||
}
|
||||
if (!shouldNormalise) {
|
||||
const expectStart = expect(this.codeContent)
|
||||
if (not) {
|
||||
return expectStart.not.toContainText(code, { timeout })
|
||||
const result = await expectStart.not.toContainText(code, { timeout })
|
||||
await resetPane()
|
||||
return result
|
||||
}
|
||||
return expectStart.toContainText(code, { timeout })
|
||||
const result = await expectStart.toContainText(code, { timeout })
|
||||
await resetPane()
|
||||
return result
|
||||
}
|
||||
const normalisedCode = code.replaceAll(/\s+/g, '').trim()
|
||||
const expectStart = expect.poll(
|
||||
@ -56,9 +75,13 @@ export class EditorFixture {
|
||||
}
|
||||
)
|
||||
if (not) {
|
||||
return expectStart.not.toContain(normalisedCode)
|
||||
const result = await expectStart.not.toContain(normalisedCode)
|
||||
await resetPane()
|
||||
return result
|
||||
}
|
||||
return expectStart.toContain(normalisedCode)
|
||||
const result = await expectStart.toContain(normalisedCode)
|
||||
await resetPane()
|
||||
return result
|
||||
}
|
||||
expectEditor = {
|
||||
toContain: this._expectEditorToContain(),
|
||||
@ -115,4 +138,13 @@ export class EditorFixture {
|
||||
code = code.replace(findCode, replaceCode)
|
||||
await this.codeContent.fill(code)
|
||||
}
|
||||
checkIfPaneIsOpen() {
|
||||
return checkIfPaneIsOpen(this.page, this.paneButtonTestId)
|
||||
}
|
||||
closePane() {
|
||||
return closePane(this.page, this.paneButtonTestId)
|
||||
}
|
||||
openPane() {
|
||||
return openPane(this.page, this.paneButtonTestId)
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ export class AuthenticatedApp {
|
||||
public readonly page: Page
|
||||
public readonly context: BrowserContext
|
||||
public readonly testInfo: TestInfo
|
||||
public readonly viewPortSize = { width: 1000, height: 500 }
|
||||
|
||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||
this.page = page
|
||||
@ -36,7 +37,7 @@ export class AuthenticatedApp {
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
}, code)
|
||||
|
||||
await this.page.setViewportSize({ width: 1000, height: 500 })
|
||||
await this.page.setViewportSize(this.viewPortSize)
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
}
|
||||
|
@ -10,7 +10,13 @@ import {
|
||||
} from '../test-utils'
|
||||
|
||||
type mouseParams = {
|
||||
pixelDiff: number
|
||||
pixelDiff?: number
|
||||
}
|
||||
type mouseDragToParams = mouseParams & {
|
||||
fromPoint: { x: number; y: number }
|
||||
}
|
||||
type mouseDragFromParams = mouseParams & {
|
||||
toPoint: { x: number; y: number }
|
||||
}
|
||||
|
||||
type SceneSerialised = {
|
||||
@ -20,6 +26,13 @@ type SceneSerialised = {
|
||||
}
|
||||
}
|
||||
|
||||
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
||||
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
|
||||
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
|
||||
type DragFromHandler = (
|
||||
dragParams: mouseDragFromParams
|
||||
) => Promise<void | boolean>
|
||||
|
||||
export class SceneFixture {
|
||||
public page: Page
|
||||
|
||||
@ -55,7 +68,7 @@ export class SceneFixture {
|
||||
x: number,
|
||||
y: number,
|
||||
{ steps }: { steps: number } = { steps: 20 }
|
||||
) =>
|
||||
): [ClickHandler, MoveHandler] =>
|
||||
[
|
||||
(clickParams?: mouseParams) => {
|
||||
if (clickParams?.pixelDiff) {
|
||||
@ -78,6 +91,47 @@ export class SceneFixture {
|
||||
return this.page.mouse.move(x, y, { steps })
|
||||
},
|
||||
] as const
|
||||
makeDragHelpers = (
|
||||
x: number,
|
||||
y: number,
|
||||
{ steps }: { steps: number } = { steps: 20 }
|
||||
): [DragToHandler, DragFromHandler] =>
|
||||
[
|
||||
(dragToParams: mouseDragToParams) => {
|
||||
if (dragToParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
() =>
|
||||
this.page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: dragToParams.fromPoint,
|
||||
targetPosition: { x, y },
|
||||
}),
|
||||
dragToParams.pixelDiff
|
||||
)
|
||||
}
|
||||
return this.page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: dragToParams.fromPoint,
|
||||
targetPosition: { x, y },
|
||||
})
|
||||
},
|
||||
(dragFromParams: mouseDragFromParams) => {
|
||||
if (dragFromParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
() =>
|
||||
this.page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x, y },
|
||||
targetPosition: dragFromParams.toPoint,
|
||||
}),
|
||||
dragFromParams.pixelDiff
|
||||
)
|
||||
}
|
||||
return this.page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x, y },
|
||||
targetPosition: dragFromParams.toPoint,
|
||||
})
|
||||
},
|
||||
] as const
|
||||
|
||||
/** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene.
|
||||
*
|
||||
@ -141,7 +195,7 @@ export class SceneFixture {
|
||||
}
|
||||
|
||||
waitForExecutionDone = async () => {
|
||||
await expect(this.exeIndicator).toBeVisible()
|
||||
await expect(this.exeIndicator).toBeVisible({ timeout: 30000 })
|
||||
}
|
||||
|
||||
expectPixelColor = async (
|
||||
|
@ -7,6 +7,7 @@ export class ToolbarFixture {
|
||||
|
||||
extrudeButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
lineBtn!: Locator
|
||||
rectangleBtn!: Locator
|
||||
exitSketchBtn!: Locator
|
||||
editSketchBtn!: Locator
|
||||
@ -15,6 +16,7 @@ export class ToolbarFixture {
|
||||
fileCreateToast!: Locator
|
||||
filePane!: Locator
|
||||
exeIndicator!: Locator
|
||||
treeInputField!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
@ -24,11 +26,13 @@ export class ToolbarFixture {
|
||||
this.page = page
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.lineBtn = page.getByTestId('line')
|
||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
||||
this.editSketchBtn = page.getByText('Edit Sketch')
|
||||
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
||||
this.createFileBtn = page.getByTestId('create-file-button')
|
||||
this.treeInputField = page.getByTestId('tree-input-field')
|
||||
|
||||
this.filePane = page.locator('#files-pane')
|
||||
this.fileCreateToast = page.getByText('Successfully created')
|
||||
@ -57,10 +61,15 @@ export class ToolbarFixture {
|
||||
expectFileTreeState = async (expected: string[]) => {
|
||||
await expect.poll(this._serialiseFileTree).toEqual(expected)
|
||||
}
|
||||
createFile = async ({ wait }: { wait: boolean } = { wait: false }) => {
|
||||
createFile = async (args: {
|
||||
fileName: string
|
||||
waitForToastToDisappear: boolean
|
||||
}) => {
|
||||
await this.createFileBtn.click()
|
||||
await this.treeInputField.fill(args.fileName)
|
||||
await this.treeInputField.press('Enter')
|
||||
await expect(this.fileCreateToast).toBeVisible()
|
||||
if (wait) {
|
||||
if (args.waitForToastToDisappear) {
|
||||
await this.fileCreateToast.waitFor({ state: 'detached' })
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export const isErrorWhitelisted = (exception: Error) => {
|
||||
{
|
||||
name: '"{"kind"',
|
||||
message:
|
||||
'"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"',
|
||||
'"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"',
|
||||
stack: '',
|
||||
foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
|
||||
project: 'Google Chrome',
|
||||
@ -156,8 +156,8 @@ export const isErrorWhitelisted = (exception: Error) => {
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message:
|
||||
'{"kind":"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}',
|
||||
stack: `Unhandled Promise Rejection: {"kind":"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: \`JsValue(undefined)\`"}
|
||||
'{"kind":"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}',
|
||||
stack: `Unhandled Promise Rejection: {"kind":"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: \`JsValue(undefined)\`"}
|
||||
at unknown (http://localhost:3000/src/lang/std/engineConnection.ts:1245:26)`,
|
||||
foundInSpec:
|
||||
'e2e/playwright/onboarding-tests.spec.ts Click through each onboarding step',
|
||||
@ -253,7 +253,7 @@ export const isErrorWhitelisted = (exception: Error) => {
|
||||
{
|
||||
name: '{"kind"',
|
||||
stack: ``,
|
||||
message: `engine","sourceRanges":[[0,0]],"msg":"Failed to wait for promise from engine: JsValue(\\"Force interrupt, executionIsStale, new AST requested\\")"}`,
|
||||
message: `engine","sourceRanges":[[0,0,0]],"msg":"Failed to wait for promise from engine: JsValue(\\"Force interrupt, executionIsStale, new AST requested\\")"}`,
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
|
||||
},
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
createProject,
|
||||
} from './test-utils'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
@ -55,6 +56,48 @@ test.describe('Onboarding tests', () => {
|
||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||
})
|
||||
|
||||
test(
|
||||
'Desktop: fresh onboarding executes and loads',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
await test.step(`Create a project and open to the onboarding`, async () => {
|
||||
await createProject({ name: 'project-link', page })
|
||||
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Ensure we see the onboarding stuff`, async () => {
|
||||
// Test that the onboarding pane loaded
|
||||
await expect(
|
||||
page.getByText('Welcome to Modeling App! This')
|
||||
).toBeVisible()
|
||||
|
||||
// *and* that the code is shown in the editor
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
'// Shelf Bracket'
|
||||
)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test('Code resets after confirmation', async ({ page }) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')`
|
||||
|
||||
@ -378,7 +421,9 @@ test(
|
||||
const restartConfirmationButton = page.getByRole('button', {
|
||||
name: 'Make a new project',
|
||||
})
|
||||
const tutorialProjectIndicator = page.getByText('Tutorial Project 00')
|
||||
const tutorialProjectIndicator = page
|
||||
.getByTestId('project-sidebar-toggle')
|
||||
.filter({ hasText: 'Tutorial Project 00' })
|
||||
const tutorialModalText = page.getByText('Welcome to Modeling App!')
|
||||
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
|
||||
const userMenuButton = page.getByTestId('user-sidebar-toggle')
|
||||
|
@ -451,3 +451,103 @@ sketch002 = startSketchOn(extrude001, seg03)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||
app,
|
||||
editor,
|
||||
toolbar,
|
||||
scene,
|
||||
}) => {
|
||||
// Constants and locators
|
||||
// These are mappings from screenspace to KCL coordinates,
|
||||
// until we merge in our coordinate system helpers
|
||||
const xzPlane = [
|
||||
app.viewPortSize.width * 0.65,
|
||||
app.viewPortSize.height * 0.3,
|
||||
] as const
|
||||
const originSloppy = {
|
||||
screen: [
|
||||
app.viewPortSize.width / 2 + 3, // 3px off the center of the screen
|
||||
app.viewPortSize.height / 2,
|
||||
],
|
||||
kcl: [0, 0],
|
||||
} as const
|
||||
const xAxisSloppy = {
|
||||
screen: [
|
||||
app.viewPortSize.width * 0.75,
|
||||
app.viewPortSize.height / 2 - 3, // 3px off the X-axis
|
||||
],
|
||||
kcl: [16.95, 0],
|
||||
} as const
|
||||
const offYAxis = {
|
||||
screen: [
|
||||
app.viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range
|
||||
app.viewPortSize.height * 0.3,
|
||||
],
|
||||
kcl: [6.78, 6.78],
|
||||
} as const
|
||||
const yAxisSloppy = {
|
||||
screen: [
|
||||
app.viewPortSize.width / 2 + 5, // 5px off the Y-axis
|
||||
app.viewPortSize.height * 0.3,
|
||||
],
|
||||
kcl: [0, 6.78],
|
||||
} as const
|
||||
const [clickOnXzPlane, moveToXzPlane] = scene.makeMouseHelpers(...xzPlane)
|
||||
const [clickOriginSloppy] = scene.makeMouseHelpers(...originSloppy.screen)
|
||||
const [clickXAxisSloppy, moveXAxisSloppy] = scene.makeMouseHelpers(
|
||||
...xAxisSloppy.screen
|
||||
)
|
||||
const [dragToOffYAxis, dragFromOffAxis] = scene.makeDragHelpers(
|
||||
...offYAxis.screen
|
||||
)
|
||||
|
||||
const expectedCodeSnippets = {
|
||||
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
|
||||
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
|
||||
segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
|
||||
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
|
||||
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
|
||||
}
|
||||
|
||||
await app.initialise()
|
||||
|
||||
await test.step(`Start a sketch on the XZ plane`, async () => {
|
||||
await editor.closePane()
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
await moveToXzPlane()
|
||||
await clickOnXzPlane()
|
||||
// timeout wait for engine animation is unavoidable
|
||||
await app.page.waitForTimeout(600)
|
||||
await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane)
|
||||
})
|
||||
await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => {
|
||||
await clickOriginSloppy()
|
||||
await editor.expectEditor.toContain(expectedCodeSnippets.pointAtOrigin)
|
||||
})
|
||||
await test.step(`Add a segment on x-axis after moving the mouse a bit, verify it snaps`, async () => {
|
||||
await moveXAxisSloppy()
|
||||
await clickXAxisSloppy()
|
||||
await editor.expectEditor.toContain(expectedCodeSnippets.segmentOnXAxis)
|
||||
})
|
||||
await test.step(`Unequip line tool`, async () => {
|
||||
await toolbar.lineBtn.click()
|
||||
await expect(toolbar.lineBtn).not.toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
await test.step(`Drag the origin point up and to the right, verify it's past snapping`, async () => {
|
||||
await dragToOffYAxis({
|
||||
fromPoint: { x: originSloppy.screen[0], y: originSloppy.screen[1] },
|
||||
})
|
||||
await editor.expectEditor.toContain(
|
||||
expectedCodeSnippets.afterSegmentDraggedOffYAxis
|
||||
)
|
||||
})
|
||||
await test.step(`Drag the origin point left to the y-axis, verify it snaps back`, async () => {
|
||||
await dragFromOffAxis({
|
||||
toPoint: { x: yAxisSloppy.screen[0], y: yAxisSloppy.screen[1] },
|
||||
})
|
||||
await editor.expectEditor.toContain(
|
||||
expectedCodeSnippets.afterSegmentDraggedOnYAxis
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
Paths,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
createProjectAndRenameIt,
|
||||
createProject,
|
||||
} from './test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
import fs from 'fs'
|
||||
@ -255,7 +255,7 @@ test.describe('Can export from electron app', () => {
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBe(431341)
|
||||
.toBeGreaterThan(300_000)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
@ -503,21 +503,261 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test.describe(`Project management commands`, () => {
|
||||
test(
|
||||
`Rename from project page`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const projectName = `my_project_to_rename`
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/${projectName}/main.kcl`
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'rename project' })
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const projectRenamedName = `project-000`
|
||||
// const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const commandContinueButton = page.getByRole('button', {
|
||||
name: 'Continue',
|
||||
})
|
||||
const commandSubmitButton = page.getByRole('button', {
|
||||
name: 'Submit command',
|
||||
})
|
||||
const toastMessage = page.getByText(`Successfully renamed`)
|
||||
|
||||
await test.step(`Setup`, async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await projectHomeLink.click()
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
await test.step(`Run rename command via command palette`, async () => {
|
||||
await commandButton.click()
|
||||
await commandOption.click()
|
||||
await projectNameOption.click()
|
||||
|
||||
await expect(commandContinueButton).toBeVisible()
|
||||
await commandContinueButton.click()
|
||||
|
||||
await expect(commandSubmitButton).toBeVisible()
|
||||
await commandSubmitButton.click()
|
||||
|
||||
await expect(toastMessage).toBeVisible()
|
||||
})
|
||||
|
||||
// TODO: in future I'd like the behavior to be to
|
||||
// navigate to the new project's page directly,
|
||||
// see ProjectContextProvider.tsx:158
|
||||
await test.step(`Check the project was renamed and we navigated home`, async () => {
|
||||
await expect(projectHomeLink.first()).toBeVisible()
|
||||
await expect(projectHomeLink.first()).toContainText(projectRenamedName)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`Delete from project page`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const projectName = `my_project_to_delete`
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/${projectName}/main.kcl`
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'delete project' })
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||
const commandSubmitButton = page.getByRole('button', {
|
||||
name: 'Submit command',
|
||||
})
|
||||
const toastMessage = page.getByText(`Successfully deleted`)
|
||||
const noProjectsMessage = page.getByText('No Projects found')
|
||||
|
||||
await test.step(`Setup`, async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await projectHomeLink.click()
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
await test.step(`Run delete command via command palette`, async () => {
|
||||
await commandButton.click()
|
||||
await commandOption.click()
|
||||
await projectNameOption.click()
|
||||
|
||||
await expect(commandWarning).toBeVisible()
|
||||
await expect(commandSubmitButton).toBeVisible()
|
||||
await commandSubmitButton.click()
|
||||
|
||||
await expect(toastMessage).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step(`Check the project was deleted and we navigated home`, async () => {
|
||||
await expect(noProjectsMessage).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
test(
|
||||
`Rename from home page`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const projectName = `my_project_to_rename`
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/${projectName}/main.kcl`
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'rename project' })
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const projectRenamedName = `project-000`
|
||||
const commandContinueButton = page.getByRole('button', {
|
||||
name: 'Continue',
|
||||
})
|
||||
const commandSubmitButton = page.getByRole('button', {
|
||||
name: 'Submit command',
|
||||
})
|
||||
const toastMessage = page.getByText(`Successfully renamed`)
|
||||
|
||||
await test.step(`Setup`, async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
await expect(projectHomeLink).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step(`Run rename command via command palette`, async () => {
|
||||
await commandButton.click()
|
||||
await commandOption.click()
|
||||
await projectNameOption.click()
|
||||
|
||||
await expect(commandContinueButton).toBeVisible()
|
||||
await commandContinueButton.click()
|
||||
|
||||
await expect(commandSubmitButton).toBeVisible()
|
||||
await commandSubmitButton.click()
|
||||
|
||||
await expect(toastMessage).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step(`Check the project was renamed`, async () => {
|
||||
await expect(
|
||||
page.getByRole('link', { name: projectRenamedName })
|
||||
).toBeVisible()
|
||||
await expect(projectHomeLink).not.toHaveText(projectName)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
test(
|
||||
`Delete from home page`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const projectName = `my_project_to_delete`
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/${projectName}/main.kcl`
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'delete project' })
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||
const commandSubmitButton = page.getByRole('button', {
|
||||
name: 'Submit command',
|
||||
})
|
||||
const toastMessage = page.getByText(`Successfully deleted`)
|
||||
const noProjectsMessage = page.getByText('No Projects found')
|
||||
|
||||
await test.step(`Setup`, async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
await expect(projectHomeLink).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step(`Run delete command via command palette`, async () => {
|
||||
await commandButton.click()
|
||||
await commandOption.click()
|
||||
await projectNameOption.click()
|
||||
|
||||
await expect(commandWarning).toBeVisible()
|
||||
await expect(commandSubmitButton).toBeVisible()
|
||||
await commandSubmitButton.click()
|
||||
|
||||
await expect(toastMessage).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step(`Check the project was deleted`, async () => {
|
||||
await expect(projectHomeLink).not.toBeVisible()
|
||||
await expect(noProjectsMessage).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
'File in the file pane should open with a single click',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const projectName = 'router-template-slate'
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
|
||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/router-template-slate/main.kcl`
|
||||
`${dir}/${projectName}/main.kcl`
|
||||
)
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/router-template-slate/otherThingToClickOn.kcl`
|
||||
`${dir}/${projectName}/otherThingToClickOn.kcl`
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -526,7 +766,7 @@ test(
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
await page.getByText('router-template-slate').click()
|
||||
await page.getByText(projectName).click()
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
@ -614,7 +854,7 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -643,7 +883,7 @@ test(
|
||||
page.on('console', console.log)
|
||||
|
||||
await test.step('delete the middle project, i.e. the bracket project', async () => {
|
||||
const project = page.getByText('bracket')
|
||||
const project = page.getByTestId('project-link').getByText('bracket')
|
||||
|
||||
await project.hover()
|
||||
await project.focus()
|
||||
@ -687,10 +927,10 @@ test(
|
||||
})
|
||||
|
||||
await test.step('Check we can still create a project', async () => {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
await expect(page.getByText('project-000')).toBeVisible()
|
||||
await createProject({ name: 'project-000', page, returnHome: true })
|
||||
await expect(
|
||||
page.getByTestId('project-link').filter({ hasText: 'project-000' })
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
@ -851,7 +1091,7 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
'When the project folder is empty, user can create new project and open it.',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -861,28 +1101,24 @@ test(
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
// Locators and constants
|
||||
const gizmo = page.locator('[aria-label*=gizmo]')
|
||||
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
|
||||
const pointOnModel = { x: 660, y: 250 }
|
||||
const expectedStartCamZPosition = 15633.47
|
||||
|
||||
// Constants and locators
|
||||
const projectLinks = page.getByTestId('project-link')
|
||||
|
||||
// expect to see text "No Projects found"
|
||||
await expect(page.getByText('No Projects found')).toBeVisible()
|
||||
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await createProject({ name: 'project-000', page, returnHome: true })
|
||||
await expect(projectLinks.getByText('project-000')).toBeVisible()
|
||||
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
await projectLinks.getByText('project-000').click()
|
||||
|
||||
await expect(page.getByText('project-000')).toBeVisible()
|
||||
|
||||
await page.getByText('project-000').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await page.locator('.cm-content').fill(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-87.4, 282.92], %)
|
||||
@ -892,8 +1128,28 @@ test(
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(200, sketch001)`)
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
const pointOnModel = { x: 660, y: 250 }
|
||||
async function getCameraZValue() {
|
||||
return page
|
||||
.getByTestId('cam-z-position')
|
||||
.inputValue()
|
||||
.then((value) => parseFloat(value))
|
||||
}
|
||||
|
||||
await test.step(`Reset camera`, async () => {
|
||||
await u.openDebugPanel()
|
||||
await u.clearCommandLogs()
|
||||
await u.doAndWaitForCmd(async () => {
|
||||
await gizmo.click({ button: 'right' })
|
||||
await resetCameraButton.click()
|
||||
}, 'zoom_to_fit')
|
||||
await expect
|
||||
.poll(getCameraZValue, {
|
||||
message: 'Camera Z should be at expected position after reset',
|
||||
})
|
||||
.toEqual(expectedStartCamZPosition)
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
@ -901,7 +1157,7 @@ extrude001 = extrude(200, sketch001)`)
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [143, 143, 143]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
.toBeLessThan(30)
|
||||
|
||||
await expect(async () => {
|
||||
await page.mouse.move(0, 0, { steps: 5 })
|
||||
@ -919,16 +1175,10 @@ extrude001 = extrude(200, sketch001)`)
|
||||
page.getByRole('button', { name: 'New project' })
|
||||
).toBeVisible()
|
||||
|
||||
const createProject = async (projectNum: number) => {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
|
||||
const projectNumStr = projectNum.toString().padStart(3, '0')
|
||||
await expect(page.getByText(`project-${projectNumStr}`)).toBeVisible()
|
||||
}
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
await createProject(i)
|
||||
const name = `project-${i.toString().padStart(3, '0')}`
|
||||
await createProject({ name, page, returnHome: true })
|
||||
await expect(projectLinks.getByText(name)).toBeVisible()
|
||||
}
|
||||
await electronApp.close()
|
||||
}
|
||||
@ -1103,11 +1353,10 @@ test(
|
||||
await page.getByTestId('settings-close-button').click()
|
||||
|
||||
await expect(page.getByText('No Projects found')).toBeVisible()
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
|
||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
||||
await createProject({ name: 'project-000', page, returnHome: true })
|
||||
await expect(
|
||||
page.getByTestId('project-link').filter({ hasText: 'project-000' })
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('We can change back to the original root project directory', async () => {
|
||||
@ -1241,13 +1490,13 @@ test(
|
||||
'function_sketch.kcl',
|
||||
'function_sketch_with_position.kcl',
|
||||
'global-tags.kcl',
|
||||
'helix_ccw.kcl',
|
||||
'helix_defaults.kcl',
|
||||
'helix_defaults_negative_extrude.kcl',
|
||||
'helix_with_length.kcl',
|
||||
'i_shape.kcl',
|
||||
'kittycad_svg.kcl',
|
||||
'lego.kcl',
|
||||
'lsystem.kcl',
|
||||
'math.kcl',
|
||||
'member_expression_sketch.kcl',
|
||||
'mike_stress_test.kcl',
|
||||
@ -1420,7 +1669,8 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
// Flaky
|
||||
test.fixme(
|
||||
'Original project name persist after onboarding',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -1433,7 +1683,7 @@ test(
|
||||
page.on('console', console.log)
|
||||
|
||||
await test.step('Should create and name a project called wrist brace', async () => {
|
||||
await createProjectAndRenameIt({ name: 'wrist brace', page })
|
||||
await createProject({ name: 'wrist brace', page, returnHome: true })
|
||||
})
|
||||
|
||||
await test.step('Should go through onboarding', async () => {
|
||||
|
@ -115,7 +115,7 @@ test.describe('Sketch tests', () => {
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> xLine(12.73, %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)`
|
||||
)
|
||||
})
|
||||
@ -156,7 +156,7 @@ test.describe('Sketch tests', () => {
|
||||
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
|> line([-12.34, 12.34], %)
|
||||
|> yLine(12.34, %)
|
||||
|
||||
`)
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
@ -202,35 +202,19 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const viewport = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewport)
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.closeDebugPanel()
|
||||
const center = {
|
||||
x: viewport.width / 2,
|
||||
y: viewport.height / 2,
|
||||
}
|
||||
const modelAreaSize = await u.getModelViewAreaSize()
|
||||
|
||||
// If we have the code pane open, we should see the code.
|
||||
if (openPanes.includes('code')) {
|
||||
@ -244,7 +228,7 @@ test.describe('Sketch tests', () => {
|
||||
await expect(u.codeLocator).not.toBeVisible()
|
||||
}
|
||||
|
||||
const startPX = [665, 458]
|
||||
const startPX = [center.x + 65, 458]
|
||||
|
||||
const dragPX = 30
|
||||
let prevContent = ''
|
||||
@ -255,7 +239,7 @@ test.describe('Sketch tests', () => {
|
||||
// Wait for the render.
|
||||
await page.waitForTimeout(1000)
|
||||
// Select the sketch
|
||||
await page.mouse.click(700, 370)
|
||||
await page.mouse.click(center.x + 100, 370)
|
||||
}
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
@ -266,45 +250,74 @@ test.describe('Sketch tests', () => {
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 - modelAreaSize.w },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(1000)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const step5 = { steps: 5 }
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
test.step('drag startProfileAt handle', async () => {
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
})
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
|
||||
// drag line handle
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.mouse.move(lineEnd.x - 5, lineEnd.y)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
test.step('drag line handle', async () => {
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.mouse.move(lineEnd.x - 5, lineEnd.y)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
})
|
||||
|
||||
// drag tangentialArcTo handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(tangentEnd.x + dragPX, tangentEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
}
|
||||
test.step('drag tangentialArcTo handle', async () => {
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(
|
||||
tangentEnd.x + dragPX,
|
||||
tangentEnd.y - dragPX,
|
||||
step5
|
||||
)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
}
|
||||
})
|
||||
|
||||
// Open the code pane
|
||||
await u.openKclCodePanel()
|
||||
@ -580,7 +593,7 @@ test.describe('Sketch tests', () => {
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [665, 458]
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
|
||||
const dragPX = 30
|
||||
|
||||
@ -596,7 +609,7 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
// drag startProfileAt handle
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||
@ -637,8 +650,8 @@ test.describe('Sketch tests', () => {
|
||||
|> revolve({ axis: "X" }, %)`)
|
||||
})
|
||||
test('Can add multiple sketches', async ({ page }) => {
|
||||
test.skip(process.platform === 'darwin', 'Can add multiple sketches')
|
||||
const u = await getUtils(page)
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
@ -646,7 +659,7 @@ test.describe('Sketch tests', () => {
|
||||
await u.openDebugPanel()
|
||||
|
||||
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||
const { toSU, click00r } = getMovementUtils({ center, page })
|
||||
const { toSU, toU, click00r } = getMovementUtils({ center, page })
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
@ -662,28 +675,32 @@ test.describe('Sketch tests', () => {
|
||||
200
|
||||
)
|
||||
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
|
||||
let codeStr = "sketch001 = startSketchOn('XY')"
|
||||
|
||||
await page.mouse.click(center.x, viewportSize.height * 0.55)
|
||||
await page.mouse.click(center.x - 50, viewportSize.height * 0.55)
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
await u.closeDebugPanel()
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
await click00r(0, 0)
|
||||
codeStr += ` |> startProfileAt(${toSU([0, 0])}, %)`
|
||||
const { click00r } = await getMovementUtils({ center, page })
|
||||
|
||||
let coord = await click00r(0, 0)
|
||||
codeStr += ` |> startProfileAt(${coord.kcl}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(50, 0)
|
||||
await page.waitForTimeout(100)
|
||||
codeStr += ` |> line(${toSU([50, 0])}, %)`
|
||||
codeStr += ` |> xLine(${toU(50, 0)[0]}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(0, 50)
|
||||
codeStr += ` |> line(${toSU([0, 50])}, %)`
|
||||
codeStr += ` |> yLine(${toU(0, 50)[1]}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(-50, 0)
|
||||
codeStr += ` |> line(${toSU([-50, 0])}, %)`
|
||||
codeStr += ` |> xLine(${toU(-50, 0)[0]}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
// exit the sketch, reset relative clicker
|
||||
@ -699,26 +716,29 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
// when exiting the sketch above the camera is still looking down at XY,
|
||||
// so selecting the plane again is a bit easier.
|
||||
await page.mouse.click(center.x + 200, center.y + 100)
|
||||
await page.mouse.move(center.x - 100, center.y + 50, { steps: 5 })
|
||||
await page.mouse.click(center.x - 100, center.y + 50)
|
||||
await page.waitForTimeout(600) // TODO detect animation ending, or disable animation
|
||||
codeStr += "sketch002 = startSketchOn('XY')"
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await click00r(30, 0)
|
||||
codeStr += ` |> startProfileAt([2.03, 0], %)`
|
||||
coord = await click00r(30, 0)
|
||||
codeStr += ` |> startProfileAt(${coord.kcl}, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
// TODO: I couldn't use `toSU` here because of some rounding error causing
|
||||
// it to be off by 0.01
|
||||
await click00r(30, 0)
|
||||
codeStr += ` |> line([2.04, 0], %)`
|
||||
codeStr += ` |> xLine(2.04, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(0, 30)
|
||||
codeStr += ` |> line([0, -2.03], %)`
|
||||
codeStr += ` |> yLine(-2.03, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(-30, 0)
|
||||
codeStr += ` |> line([-2.04, 0], %)`
|
||||
codeStr += ` |> xLine(-2.04, %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(undefined, undefined)
|
||||
@ -742,8 +762,8 @@ test.describe('Sketch tests', () => {
|
||||
|
||||
const code = `sketch001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|
||||
|> line([${roundOff(scale * 139.19)}, 0], %)
|
||||
|> line([0, -${roundOff(scale * 139.2)}], %)
|
||||
|> xLine(${roundOff(scale * 139.19)}, %)
|
||||
|> yLine(-${roundOff(scale * 139.2)}, %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
|
||||
@ -762,20 +782,21 @@ test.describe('Sketch tests', () => {
|
||||
await u.updateCamPosition(camPos)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
await page.mouse.move(0, 0)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.move(700, 200, { steps: 10 })
|
||||
await page.mouse.click(700, 200, { delay: 200 })
|
||||
await page.mouse.move(center.x + 100, 200, { steps: 10 })
|
||||
await page.mouse.click(center.x + 100, 200, { delay: 200 })
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('-XZ')`
|
||||
)
|
||||
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
const pointA = [700, 200]
|
||||
const pointB = [900, 200]
|
||||
const pointC = [900, 400]
|
||||
const pointA = [center.x + 100, 200]
|
||||
const pointB = [center.x + 300, 200]
|
||||
const pointC = [center.x + 300, 400]
|
||||
|
||||
// draw three lines
|
||||
await page.waitForTimeout(500)
|
||||
@ -912,7 +933,9 @@ extrude001 = extrude(5, sketch001)
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
await page.mouse.click(622, 355)
|
||||
const center = await u.getCenterOfModelViewArea()
|
||||
|
||||
await page.mouse.click(center.x + 22, 355)
|
||||
|
||||
await page.waitForTimeout(800)
|
||||
await page.getByText(`END')`).click()
|
||||
|
@ -462,7 +462,7 @@ test(
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
code += `
|
||||
|> line([7.25, 0], %)`
|
||||
|> xLine(7.25, %)`
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
|
||||
await page
|
||||
@ -471,7 +471,7 @@ test(
|
||||
|
||||
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
@ -528,6 +528,7 @@ test(
|
||||
// Draw the rectangle
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 30)
|
||||
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 })
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
// Ensure the draft rectangle looks the same as it usually does
|
||||
await expect(page).toHaveScreenshot({
|
||||
@ -646,7 +647,7 @@ test.describe(
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
code += `
|
||||
|> line([7.25, 0], %)`
|
||||
|> xLine(7.25, %)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
@ -751,7 +752,7 @@ test.describe(
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
code += `
|
||||
|> line([184.3, 0], %)`
|
||||
|> xLine(184.3, %)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
@ -895,7 +896,7 @@ test(
|
||||
// Wait for the second extrusion to appear
|
||||
// TODO: Find a way to truly know that the objects have finished
|
||||
// rendering, because an execution-done message is not sufficient.
|
||||
await page.waitForTimeout(1000)
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
@ -939,7 +940,7 @@ test(
|
||||
// Wait for the second extrusion to appear
|
||||
// TODO: Find a way to truly know that the objects have finished
|
||||
// rendering, because an execution-done message is not sufficient.
|
||||
await page.waitForTimeout(1000)
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
@ -1030,7 +1031,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('theme persists', async ({ page, context }) => {
|
||||
test.fixme('theme persists', async ({ page, context }) => {
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
@ -141,7 +141,7 @@ test.describe('Test network and connection issues', () => {
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> line([${commonPoints.num1}, 0], %)`)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
@ -207,7 +207,7 @@ test.describe('Test network and connection issues', () => {
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
|> line([12.34, 0], %)
|
||||
|> xLine(12.34, %)
|
||||
|> line([-12.34, 12.34], %)
|
||||
|
||||
`)
|
||||
@ -217,9 +217,9 @@ test.describe('Test network and connection issues', () => {
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
|> line([12.34, 0], %)
|
||||
|> xLine(12.34, %)
|
||||
|> line([-12.34, 12.34], %)
|
||||
|> line([-12.34, 0], %)
|
||||
|> xLine(-12.34, %)
|
||||
|
||||
`)
|
||||
|
||||
|