Compare commits
265 Commits
v0.24.12
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
58b643cedd | |||
5512f99997 | |||
01cc9e751b | |||
bfac6b7dc8 | |||
d1f9a02ffa | |||
d8236dd8da | |||
dabf256e2b | |||
4285e81001 | |||
370375c328 | |||
9f22882c68 | |||
db5331d9b9 | |||
5cc92f0162 | |||
2978e80226 | |||
4a74c60150 | |||
00fa40bbc9 | |||
62b78840b6 | |||
f828c36e58 | |||
8c5b146c94 | |||
61c7d9844d | |||
8d48c17395 | |||
0ff820d4da | |||
c4ff1c2ef1 | |||
b6aba2f29c | |||
7467f7ea50 | |||
0c6d3e0ccf | |||
e82917ea01 | |||
857c1aad3d | |||
dc73acb1b1 | |||
8602e937d3 | |||
a2133d8317 | |||
39ce0da3e5 | |||
f235a950b0 | |||
3cd3e1af72 | |||
8c6266e94b | |||
755a6016c7 | |||
1cbbefba97 | |||
8610d606f4 | |||
728e87a627 | |||
772034af68 | |||
957a9ca4fe | |||
472eb2bafe | |||
88216d4c76 | |||
8b1e4d6708 | |||
769c3ec785 | |||
1c4179a9db | |||
292f89859f | |||
a00800bddc | |||
78ceba6d20 | |||
6776a350af | |||
dd75f06f77 | |||
394872d84e | |||
f9eef6397f | |||
900bac999c | |||
5b2738f826 | |||
dab96577a7 | |||
25443eba31 | |||
0a72d7a39a | |||
5f8d4f8294 | |||
7c2cfba0ac | |||
5ee43bda22 | |||
a1b6bbac7e | |||
e61516f3c3 | |||
e2eeec37ad | |||
d7fcc128aa | |||
cf266b17c1 | |||
b3a1796da9 | |||
39b9a6b2c4 | |||
6ba4fa305c | |||
1d043899c8 | |||
cb8a087d89 | |||
f2eb7b57b8 | |||
eba653930f | |||
3deb5c689a | |||
11ebe11111 | |||
9538ffb8ec | |||
55d1da226f | |||
2bfde64bf1 | |||
7cb9a2efd9 | |||
57e85d7fd0 | |||
ca4a442cce | |||
46eef39d53 | |||
dbc5f7b11f | |||
6797331c9d | |||
cc80a2da3d | |||
54fb9c903a | |||
e63597458a | |||
e15c38fa23 | |||
906ca65611 | |||
805b9f48e5 | |||
a762d741a5 | |||
4b8ca7f61f | |||
31b0a8af12 | |||
74b4cb9e08 | |||
e7c6dd3698 | |||
aa9abbe83f | |||
b19f3bbdb0 | |||
892e856471 | |||
84fae12cdd | |||
3d67781039 | |||
114c3a2580 | |||
02b4aa0476 | |||
57f4e1b79c | |||
35f9b82a65 | |||
cbddb3553d | |||
dd754c78ab | |||
150f56b47a | |||
0eef6ab7d3 | |||
91d3ba3fce | |||
7165aa1b41 | |||
3cbda10eab | |||
0f3432b5a0 | |||
f11dc07f0b | |||
e49beb6609 | |||
b8f27b77a8 | |||
fa7e31223d | |||
f04c4588df | |||
c95812efa6 | |||
96385cd5ee | |||
64707edaad | |||
27baf135e7 | |||
a4cf68c661 | |||
403e074249 | |||
50259aa052 | |||
1739f3dafe | |||
7ceb518446 | |||
36a6b8c0ea | |||
bbdca7421e | |||
03c6f6d60e | |||
18c7e7934a | |||
bf650fd129 | |||
81ccb65f15 | |||
335b5100ae | |||
1162ff3b03 | |||
5e8227ead8 | |||
ed339a6b9a | |||
1d19fc6b7e | |||
5b5355376f | |||
5c90f72c91 | |||
026a8d19cb | |||
6dd0981709 | |||
b231a26115 | |||
3f47486fb5 | |||
57e97d16d0 | |||
dbdc7e5c8b | |||
f6bb10170d | |||
972dca8743 | |||
e9e933eecd | |||
2b1315423f | |||
bd4c24bc04 | |||
50cc88977c | |||
bea9a1c3ec | |||
f43411fdb4 | |||
c2e9d18f92 | |||
199722c505 | |||
f9699d174c | |||
590a6479e0 | |||
fbf0d3d953 | |||
3dd66bc8d2 | |||
a928b8fbd0 | |||
d2349bec2b | |||
6e10f75ff6 | |||
03e289af20 | |||
efc140abbf | |||
4dfad19b7e | |||
e56c634b35 | |||
00292abc98 | |||
483d6903d6 | |||
3780996374 | |||
2fde71228a | |||
5cd8ab3812 | |||
9a385fb474 | |||
b740d25bbd | |||
ef350b020b | |||
4d2375faac | |||
22a9f44916 | |||
713a30ed72 | |||
ebed10bc76 | |||
acbe92d717 | |||
e624c9b124 | |||
877eb3ec5e | |||
64500d055a | |||
5df996d877 | |||
84d70751af | |||
3899999465 | |||
9f370fbb56 | |||
f750c4ea8b | |||
e16ecc28a3 | |||
a2d8c5a714 | |||
0bb4586e6d | |||
bbabf04ba6 | |||
37a1208924 | |||
682099c1ad | |||
8f3ad0d43c | |||
be047f5111 | |||
d656a389f8 | |||
682590deea | |||
925f5cc2c2 | |||
a167c174f9 | |||
7f297c13fd | |||
a7e3d83297 | |||
f74c12aa99 | |||
5df9965795 | |||
50d80eb0b6 | |||
96d24065d6 | |||
61dc94b1ee | |||
f14c27e1c4 | |||
c09775f5eb | |||
d14b8f5443 | |||
4a14ca38ab | |||
3543c5f0e7 | |||
a0dc5f4a89 | |||
9d148938a2 | |||
9c6cca2944 | |||
5c472c63d2 | |||
f77b312ecb | |||
8a66d0df76 | |||
b3dc3ff78c | |||
d02df08471 | |||
aac758b396 | |||
0ef6eac239 | |||
c674feb782 | |||
fba3d7c5c1 | |||
8b8fb696d0 | |||
d05f3c00b9 | |||
2541e0c0ea | |||
5e5a204244 | |||
032c2fdd24 | |||
27883e7800 | |||
1ccb810e23 | |||
1c83f148d9 | |||
c7f533b38e | |||
2b711d216f | |||
c67511f67c | |||
d9423219d1 | |||
3f270d8bcf | |||
4c7b72329d | |||
4c060f3d2f | |||
f3afbe8a7b | |||
dad7a84798 | |||
1a560fdc6a | |||
2d6c8cfe32 | |||
37c6730c02 | |||
337f828aa4 | |||
d845e7c38d | |||
7f50294936 | |||
73bbd3f5b7 | |||
295b98c021 | |||
2e24137863 | |||
5e694961e8 | |||
a1ef4ff86f | |||
ccd31b7d6d | |||
b5ddbb7fa7 | |||
4613a7c92e | |||
a89d8bb8e8 | |||
1822021bb3 | |||
9d71900caf | |||
0c15299b0e | |||
4def38a698 | |||
9e4671c6d7 | |||
b2707ecc41 | |||
53e0277acc | |||
e8d90f171b | |||
a6c5493a7f | |||
2a10688b39 | |||
8400e06dd6 |
@ -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,./src-tauri/gen/schemas
|
||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock
|
||||
|
@ -1,6 +1,10 @@
|
||||
NODE_ENV=development
|
||||
DEV=true
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
||||
BASE_URL=https://api.dev.zoo.dev
|
||||
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||
VITE_KC_SKIP_AUTH=false
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||
VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
||||
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
|
||||
#VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
||||
|
@ -13,6 +13,8 @@
|
||||
"plugin:css-modules/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
@ -24,8 +26,9 @@
|
||||
{
|
||||
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
|
||||
"rules": {
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"testing-library/prefer-screen-queries": "off"
|
||||
"suggest-no-throw/suggest-no-throw": "off",
|
||||
"testing-library/prefer-screen-queries": "off",
|
||||
"jest/valid-expect": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
9
.github/dependabot.yml
vendored
@ -10,7 +10,7 @@ updates:
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- franknoirot
|
||||
- franknoirot
|
||||
- irev-dev
|
||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
@ -26,10 +26,3 @@ updates:
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src-tauri/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
|
323
.github/workflows/build-test-publish-apps.yml
vendored
Normal file
@ -0,0 +1,323 @@
|
||||
name: build-publish-apps
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types: [published]
|
||||
schedule:
|
||||
- cron: '0 4 * * *'
|
||||
# Daily at 04:00 AM UTC
|
||||
# Will checkout the last commit from the default branch (main as of 2023-10-04)
|
||||
|
||||
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')) }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
prepare-files:
|
||||
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||
outputs:
|
||||
version: ${{ steps.export_version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
# TODO: see if we can fetch from main instead if no diff at src/wasm-lib
|
||||
- name: Run build:wasm
|
||||
run: "yarn build:wasm"
|
||||
|
||||
- name: Set nightly version
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
|
||||
|
||||
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prepared-files
|
||||
path: |
|
||||
package.json
|
||||
src/wasm-lib/pkg/wasm_lib*
|
||||
|
||||
- id: export_version
|
||||
run: echo "version=`cat package.json | jq -r '.version'`" >> "$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"' electron-builder.yml
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prepared-files-updater-test
|
||||
path: |
|
||||
electron-builder.yml
|
||||
|
||||
|
||||
build-apps:
|
||||
needs: [prepare-files]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-14, windows-2022, ubuntu-22.04]
|
||||
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
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
name: prepared-files
|
||||
|
||||
- name: Copy prepared files
|
||||
run: |
|
||||
ls -R prepared-files
|
||||
cp prepared-files/package.json package.json
|
||||
cp prepared-files/src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
mkdir src/wasm-lib/pkg
|
||||
cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- run: yarn tronb:vite
|
||||
|
||||
- name: Prepare certificate and variables (Windows only)
|
||||
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }}
|
||||
run: |
|
||||
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
|
||||
cat /d/Certificate_pkcs12.p12
|
||||
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
|
||||
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
|
||||
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
|
||||
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
|
||||
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
|
||||
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- name: Setup certicate with SSM KSP (Windows only)
|
||||
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }}
|
||||
run: |
|
||||
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
|
||||
msiexec /i smtools-windows-x64.msi /quiet /qn
|
||||
smksp_registrar.exe list
|
||||
smctl.exe keypair ls
|
||||
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||
smksp_cert_sync.exe
|
||||
shell: cmd
|
||||
|
||||
- name: Build the app
|
||||
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
|
||||
|
||||
- name: List artifacts in out/
|
||||
run: ls -R out
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: out-${{ matrix.os }}
|
||||
path: |
|
||||
out/Zoo*.*
|
||||
out/latest*.yml
|
||||
|
||||
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
name: prepared-files-updater-test
|
||||
|
||||
- name: Copy updated electron-builder.yml file for updater test
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
ls -R prepared-files-updater-test
|
||||
cp prepared-files-updater-test/electron-builder.yml electron-builder.yml
|
||||
|
||||
- name: Build the app (updater-test)
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
name: updater-test-${{ matrix.os }}
|
||||
path: |
|
||||
out/Zoo*.*
|
||||
out/latest*.yml
|
||||
|
||||
|
||||
publish-apps-release:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
|
||||
needs: [prepare-files, build-apps]
|
||||
env:
|
||||
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: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
||||
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' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-windows-2022
|
||||
path: out
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-macos-14
|
||||
path: out
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-ubuntu-22.04
|
||||
path: out
|
||||
|
||||
- name: Generate the download static endpoint
|
||||
run: |
|
||||
RELEASE_DIR=https://${WEBSITE_DIR}
|
||||
jq --null-input \
|
||||
--arg version "${VERSION}" \
|
||||
--arg pub_date "${PUB_DATE}" \
|
||||
--arg notes "${NOTES}" \
|
||||
--arg mac_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-mac.dmg" \
|
||||
--arg mac_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-mac.dmg" \
|
||||
--arg windows_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-win.exe" \
|
||||
--arg windows_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-win.exe" \
|
||||
--arg linux_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-linux.AppImage" \
|
||||
--arg linux_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x86_64-linux.AppImage" \
|
||||
'{
|
||||
"version": $version,
|
||||
"pub_date": $pub_date,
|
||||
"notes": $notes,
|
||||
"platforms": {
|
||||
"dmg-arm64": {
|
||||
"url": $mac_arm64_url
|
||||
},
|
||||
"dmg-x64": {
|
||||
"url": $mac_x64_url
|
||||
},
|
||||
"exe-arm64": {
|
||||
"url": $windows_arm64_url
|
||||
},
|
||||
"exe-x64": {
|
||||
"url": $windows_x64_url
|
||||
},
|
||||
"appimage-arm64": {
|
||||
"url": $linux_arm64_url
|
||||
},
|
||||
"appimage-x64": {
|
||||
"url": $linux_x64_url
|
||||
}
|
||||
}
|
||||
}' > last_download.json
|
||||
cat last_download.json
|
||||
|
||||
- name: List artifacts
|
||||
run: "ls -R out"
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
uses: 'google-github-actions/auth@v2.1.5'
|
||||
with:
|
||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||
|
||||
- name: Set up Google Cloud SDK
|
||||
uses: google-github-actions/setup-gcloud@v2.1.0
|
||||
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
|
||||
with:
|
||||
path: out
|
||||
glob: 'Zoo*'
|
||||
parent: false
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload update endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
with:
|
||||
path: out
|
||||
glob: 'latest*'
|
||||
parent: false
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload download endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||
with:
|
||||
path: last_download.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload release files to Github
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: 'out/Zoo*'
|
||||
|
||||
# TODO: Add GitHub publisher
|
||||
|
||||
announce_release:
|
||||
needs: [publish-apps-release]
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event_name == 'release'
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
- name: Announce Release
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
RELEASE_VERSION: ${{ github.event.release.tag_name }}
|
||||
RELEASE_BODY: ${{ github.event.release.body}}
|
||||
run: python public/announce_release.py
|
117
.github/workflows/build-test-web.yml
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
name: build-test-web
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
check-format:
|
||||
runs-on: 'ubuntu-22.04'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- run: yarn fmt-check
|
||||
|
||||
check-types:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
- run: yarn xstate:typegen
|
||||
- run: yarn tsc
|
||||
- name: Lint
|
||||
run: yarn eslint --max-warnings 0 src e2e packages/codemirror-lsp-client
|
||||
|
||||
|
||||
check-typos:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
- name: Install codespell
|
||||
run: |
|
||||
python -m pip install codespell
|
||||
- name: Run codespell
|
||||
run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration.
|
||||
|
||||
|
||||
build-test-web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:ci
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
|
||||
- name: Install Chromium Browser
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn playwright install chromium --with-deps
|
||||
|
||||
- name: run unit tests
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn test:nowatch
|
||||
env:
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
|
||||
- name: check for changes
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
id: git-check
|
||||
run: |
|
||||
git add src/lang/std/artifactMapGraphs
|
||||
if git status src/lang/std/artifactMapGraphs | grep -q "Changes to be committed"
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' && steps.git-check.outputs.modified == 'true' }}
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
# TODO when webkit works on ubuntu remove the os part of the commit message
|
||||
git commit -am "Look at this (photo)Graph *in the voice of Nickelback*" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
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
|
||||
cargo check --all --features disable-println --features pyo3 --features cli
|
||||
|
23
.github/workflows/cargo-clippy.yml
vendored
@ -25,9 +25,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dir: ['src/wasm-lib', 'src-tauri']
|
||||
dir: ['src/wasm-lib']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: taiki-e/install-action@just
|
||||
- name: Install latest rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@ -35,31 +36,13 @@ jobs:
|
||||
override: true
|
||||
components: clippy
|
||||
|
||||
- name: install dependencies
|
||||
if: matrix.dir == 'src-tauri'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libgtk-3-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
webkit2gtk-driver \
|
||||
libsoup-3.0-dev \
|
||||
libjavascriptcoregtk-4.1-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
at-spi2-core \
|
||||
xvfb
|
||||
yarn install
|
||||
yarn build:wasm
|
||||
yarn build:local
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.6.1
|
||||
|
||||
- name: Run clippy
|
||||
run: |
|
||||
cd "${{ matrix.dir }}"
|
||||
cargo clippy --all --tests --benches -- -D warnings
|
||||
just lint
|
||||
# If this fails, run "cargo check" to update Cargo.lock,
|
||||
# then add Cargo.lock to the PR.
|
||||
- name: Check Cargo.lock doesn't need updating
|
||||
|
2
.github/workflows/cargo-fmt.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dir: ['src/wasm-lib', 'src-tauri']
|
||||
dir: ['src/wasm-lib']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install latest rust
|
||||
|
57
.github/workflows/cargo-test-tauri.yml
vendored
@ -1,57 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'src-tauri/**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-test-tauri.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src-tauri/**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-test-tauri.yml
|
||||
workflow_dispatch:
|
||||
permissions: read-all
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
name: cargo test of tauri
|
||||
jobs:
|
||||
cargotest:
|
||||
name: cargo test
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
strategy:
|
||||
matrix:
|
||||
dir: ['src-tauri']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install latest rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: install dependencies
|
||||
if: matrix.dir == 'src-tauri'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libgtk-3-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
webkit2gtk-driver \
|
||||
libsoup-3.0-dev \
|
||||
libjavascriptcoregtk-4.1-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
at-spi2-core \
|
||||
xvfb
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.6.1
|
||||
- name: cargo test
|
||||
shell: bash
|
||||
run: |-
|
||||
cd "${{ matrix.dir }}"
|
||||
cargo test --all
|
7
.github/workflows/cargo-test.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- 'src/wasm-lib/**.kcl'
|
||||
- .github/workflows/cargo-test.yml
|
||||
|
||||
pull_request:
|
||||
@ -15,6 +16,7 @@ on:
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- 'src/wasm-lib/**.kcl'
|
||||
- .github/workflows/cargo-test.yml
|
||||
workflow_dispatch:
|
||||
permissions: read-all
|
||||
@ -36,11 +38,6 @@ jobs:
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: install dependencies
|
||||
if: matrix.dir == 'src-tauri'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
- name: Install vector
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
|
586
.github/workflows/ci.yml
vendored
@ -1,586 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types: [published]
|
||||
schedule:
|
||||
- cron: '0 4 * * *'
|
||||
# Daily at 04:00 AM UTC
|
||||
# Will checkout the last commit from the default branch (main as of 2023-10-04)
|
||||
|
||||
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')) }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
check-format:
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- run: yarn fmt-check
|
||||
|
||||
check-types:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
- run: yarn xstate:typegen
|
||||
- run: yarn tsc
|
||||
|
||||
|
||||
check-typos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
- name: Install codespell
|
||||
run: |
|
||||
python -m pip install codespell
|
||||
- name: Run codespell
|
||||
run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration.
|
||||
|
||||
|
||||
build-test-web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:ci
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
|
||||
- name: Install Chromium Browser
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn playwright install chromium --with-deps
|
||||
|
||||
- name: run unit tests
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: yarn test:nowatch
|
||||
env:
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
|
||||
- name: check for changes
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
id: git-check
|
||||
run: |
|
||||
git add src/lang/std/artifactMapGraphs
|
||||
if git status src/lang/std/artifactMapGraphs | grep -q "Changes to be committed"
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' && steps.git-check.outputs.modified == 'true' }}
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
# TODO when webkit works on ubuntu remove the os part of the commit message
|
||||
git commit -am "Look at this (photo)Graph *in the voice of Nickelback*" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
prepare-json-files:
|
||||
runs-on: ubuntu-latest # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||
outputs:
|
||||
version: ${{ steps.export_version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Set nightly version
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
|
||||
echo "$(jq --arg url 'https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json' \
|
||||
'.plugins.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
||||
echo "$(jq --arg id 'dev.zoo.modeling-app-nightly' \
|
||||
'.identifier=$id' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
||||
echo "$(jq --arg name 'Zoo Modeling App (Nightly)' \
|
||||
'.productName=$name' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
||||
|
||||
- name: Set updater test version
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
echo "$(jq --arg url 'https://dl.zoo.dev/releases/modeling-app/test/last_update.json' \
|
||||
'.plugins.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ github.event_name == 'schedule' || env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
path: |
|
||||
package.json
|
||||
src-tauri/tauri.conf.json
|
||||
src-tauri/tauri.release.conf.json
|
||||
|
||||
- id: export_version
|
||||
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
build-test-apps:
|
||||
needs: [prepare-json-files]
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-14, ubuntu-latest, windows-latest]
|
||||
env:
|
||||
# Specific Apple Universal target for macos
|
||||
TAURI_ARGS_MACOS: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
|
||||
# Only build executable on linux (no appimage or deb)
|
||||
TAURI_ARGS_UBUNTU: ${{ matrix.os == 'ubuntu-latest' && '--bundles' || '' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: github.event_name == 'schedule'
|
||||
|
||||
- name: Copy updated .json files
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
ls -l artifact
|
||||
cp artifact/package.json package.json
|
||||
cp artifact/src-tauri/tauri.conf.json src-tauri/tauri.conf.json
|
||||
cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json
|
||||
|
||||
- name: Update WebView2 on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
# Workaround needed to build the tauri windows app with matching edge version.
|
||||
# From https://github.com/actions/runner-images/issues/9538
|
||||
run: |
|
||||
Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
|
||||
Start-Process -FilePath setup.exe -Verb RunAs -Wait
|
||||
|
||||
- name: Install ubuntu system dependencies
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libgtk-3-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
webkit2gtk-driver \
|
||||
libsoup-3.0-dev \
|
||||
libjavascriptcoregtk-4.1-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
at-spi2-core \
|
||||
xvfb
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- name: Run build:wasm manually
|
||||
shell: bash
|
||||
env:
|
||||
MODE: ${{ env.BUILD_RELEASE == 'true' && '--release' || '--debug' }}
|
||||
run: |
|
||||
mkdir src/wasm-lib/pkg; cd src/wasm-lib
|
||||
echo "building with ${{ env.MODE }}"
|
||||
npx wasm-pack build --target web --out-dir pkg ${{ env.MODE }}
|
||||
cd ../../
|
||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
|
||||
- name: Run vite build (build:both)
|
||||
run: yarn vite build --mode ${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||
|
||||
- name: Fix format
|
||||
run: yarn fmt
|
||||
|
||||
- name: Install x86 target for Universal builds (MacOS only)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
rustup target add x86_64-apple-darwin
|
||||
|
||||
- name: Prepare certificate and variables (Windows only)
|
||||
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
|
||||
run: |
|
||||
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
|
||||
cat /d/Certificate_pkcs12.p12
|
||||
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
|
||||
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
|
||||
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
|
||||
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
|
||||
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
|
||||
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
|
||||
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
|
||||
- name: Setup certicate with SSM KSP (Windows only)
|
||||
if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }}
|
||||
run: |
|
||||
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
|
||||
msiexec /i smtools-windows-x64.msi /quiet /qn
|
||||
smksp_registrar.exe list
|
||||
smctl.exe keypair ls
|
||||
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||
smksp_cert_sync.exe
|
||||
shell: cmd
|
||||
|
||||
- name: Build the app (debug)
|
||||
if: ${{ env.BUILD_RELEASE == 'false' }}
|
||||
run: "yarn tauri build --debug ${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
|
||||
|
||||
- name: Build for Mac TestFlight (nightly)
|
||||
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
|
||||
shell: bash
|
||||
run: |
|
||||
unset APPLE_SIGNING_IDENTITY
|
||||
unset APPLE_CERTIFICATE
|
||||
sign_app="3rd Party Mac Developer Application: KittyCAD Inc (${APPLE_TEAM_ID})"
|
||||
sign_install="3rd Party Mac Developer Installer: KittyCAD Inc (${APPLE_TEAM_ID})"
|
||||
profile="src-tauri/entitlements/Mac_App_Distribution.provisionprofile"
|
||||
|
||||
mkdir -p src-tauri/entitlements
|
||||
echo -n "${APPLE_STORE_PROVISIONING_PROFILE}" | base64 --decode -o "${profile}"
|
||||
|
||||
echo -n "${APPLE_STORE_DISTRIBUTION_CERT}" | base64 --decode -o "dist.cer"
|
||||
echo -n "${APPLE_STORE_INSTALLER_CERT}" | base64 --decode -o "installer.cer"
|
||||
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
|
||||
KEYCHAIN_PASSWORD="password"
|
||||
|
||||
# create temporary keychain
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
|
||||
# import certificate to keychain
|
||||
security import "dist.cer" -P "$APPLE_STORE_P12_PASSWORD" -k $KEYCHAIN_PATH -f pkcs12 -t cert -A
|
||||
security import "installer.cer" -P "$APPLE_STORE_P12_PASSWORD" -k $KEYCHAIN_PATH -f pkcs12 -t cert -A
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||
|
||||
target="universal-apple-darwin"
|
||||
|
||||
# Turn off the default target
|
||||
# We don't want to install the updater for the apple store build
|
||||
sed -i.bu "s/default =/# default =/" src-tauri/Cargo.toml
|
||||
rm src-tauri/Cargo.toml.bu
|
||||
git diff src-tauri/Cargo.toml
|
||||
|
||||
yarn tauri build --target "${target}" --verbose --config src-tauri/tauri.app-store.conf.json
|
||||
|
||||
app_path="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app"
|
||||
build_name="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.pkg"
|
||||
cp_dir="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app/Contents/embedded.provisionprofile"
|
||||
entitlements="src-tauri/entitlements/app-store.entitlements"
|
||||
|
||||
cp "${profile}" "${cp_dir}"
|
||||
|
||||
codesign --deep --force -s "${sign_app}" --entitlements "${entitlements}" "${app_path}"
|
||||
|
||||
productbuild --component "${app_path}" /Applications/ --sign "${sign_install}" "${build_name}"
|
||||
|
||||
# Undo the changes to the Cargo.toml
|
||||
git checkout src-tauri/Cargo.toml
|
||||
|
||||
env:
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_STORE_PROVISIONING_PROFILE: ${{ secrets.APPLE_STORE_PROVISIONING_PROFILE }}
|
||||
APPLE_STORE_DISTRIBUTION_CERT: ${{ secrets.APPLE_STORE_DISTRIBUTION_CERT }}
|
||||
APPLE_STORE_INSTALLER_CERT: ${{ secrets.APPLE_STORE_INSTALLER_CERT }}
|
||||
APPLE_STORE_P12_PASSWORD: ${{ secrets.APPLE_STORE_P12_PASSWORD }}
|
||||
|
||||
|
||||
- name: 'Upload to Mac TestFlight (nightly)'
|
||||
uses: apple-actions/upload-testflight-build@v1
|
||||
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
|
||||
with:
|
||||
app-path: 'src-tauri/target/universal-apple-darwin/release/bundle/macos/Zoo Modeling App.pkg'
|
||||
issuer-id: ${{ secrets.APPLE_STORE_ISSUER_ID }}
|
||||
api-key-id: ${{ secrets.APPLE_STORE_API_KEY_ID }}
|
||||
api-private-key: ${{ secrets.APPLE_STORE_API_PRIVATE_KEY }}
|
||||
app-type: osx
|
||||
|
||||
|
||||
- name: Clean up after Mac TestFlight (nightly)
|
||||
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
|
||||
shell: bash
|
||||
run: |
|
||||
git status
|
||||
# remove our target builds because we want to make sure the later build
|
||||
# includes the updater, and that anything we changed with the target
|
||||
# does not persist
|
||||
rm -rf src-tauri/target
|
||||
# Lets get rid of the info.plist for the normal mac builds since its
|
||||
# being sketchy.
|
||||
rm src-tauri/Info.plist
|
||||
|
||||
# We do this after the apple store because the apple store build is
|
||||
# specific and we want to overwrite it with the this new build after and
|
||||
# not upload the apple store build to the public bucket
|
||||
- name: Build the app (release) and sign
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
TAURI_CONF_ARGS: "--config ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
|
||||
run: "yarn tauri build ${{ env.TAURI_CONF_ARGS }} ${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
env:
|
||||
PREFIX: ${{ matrix.os == 'macos-14' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }}
|
||||
MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}
|
||||
with:
|
||||
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
|
||||
|
||||
- name: Run e2e tests (linux only)
|
||||
if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: |
|
||||
cargo install tauri-driver --force
|
||||
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
|
||||
export VITE_KC_API_BASE_URL
|
||||
xvfb-run yarn test:e2e:tauri
|
||||
env:
|
||||
E2E_APPLICATION: "./src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/zoo-modeling-app"
|
||||
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
|
||||
- name: Run e2e tests (windows only)
|
||||
if: ${{ matrix.os == 'windows-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||
run: |
|
||||
cargo install tauri-driver --force
|
||||
yarn wdio run wdio.conf.ts
|
||||
env:
|
||||
E2E_APPLICATION: ".\\src-tauri\\target\\${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}\\Zoo Modeling App.exe"
|
||||
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_API_BASE_URL: ${{ env.BUILD_RELEASE == 'true' && 'https://api.zoo.dev' || 'https://api.dev.zoo.dev' }}
|
||||
E2E_TAURI_ENABLED: true
|
||||
TS_NODE_COMPILER_OPTIONS: '{"module": "commonjs"}'
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
|
||||
- name: Copy updated .json file for updater test
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
ls -l artifact
|
||||
cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json
|
||||
cat src-tauri/tauri.release.conf.json
|
||||
|
||||
- name: Build the app (release, updater test)
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' && matrix.os != 'ubuntu-latest' }}
|
||||
env:
|
||||
TAURI_CONF_ARGS: "-c ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
|
||||
TAURI_BUNDLE_ARGS: "-b ${{ matrix.os == 'windows-latest' && 'msi' || 'dmg' }}"
|
||||
run: "yarn tauri build ${{ env.TAURI_CONF_ARGS }} ${{ env.TAURI_BUNDLE_ARGS }} ${{ env.TAURI_ARGS_MACOS }}"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' && matrix.os != 'ubuntu-latest' }}
|
||||
with:
|
||||
path: "${{ matrix.os == 'macos-14' && 'src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg' || 'src-tauri/target/release/bundle/msi/*.msi' }}"
|
||||
name: updater-test
|
||||
|
||||
|
||||
publish-apps-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
|
||||
needs: [check-format, check-types, check-typos, build-test-web, prepare-json-files, build-test-apps]
|
||||
env:
|
||||
VERSION_NO_V: ${{ needs.prepare-json-files.outputs.version }}
|
||||
VERSION: ${{ github.event_name == 'release' && format('v{0}', needs.prepare-json-files.outputs.version) || needs.prepare-json-files.outputs.version }}
|
||||
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }}
|
||||
BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }}
|
||||
WEBSITE_DIR: ${{ github.event_name == 'release' && 'dl.zoo.dev/releases/modeling-app' || 'dl.zoo.dev/releases/modeling-app/nightly' }}
|
||||
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
|
||||
- name: Generate the update static endpoint
|
||||
run: |
|
||||
ls -l artifact/*/*oo*
|
||||
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
||||
WINDOWS_SIG=`cat artifact/msi/*.msi.zip.sig`
|
||||
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
||||
jq --null-input \
|
||||
--arg version "${VERSION}" \
|
||||
--arg pub_date "${PUB_DATE}" \
|
||||
--arg notes "${NOTES}" \
|
||||
--arg darwin_sig "$DARWIN_SIG" \
|
||||
--arg darwin_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}.app.tar.gz" \
|
||||
--arg windows_sig "$WINDOWS_SIG" \
|
||||
--arg windows_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
||||
'{
|
||||
"version": $version,
|
||||
"pub_date": $pub_date,
|
||||
"notes": $notes,
|
||||
"platforms": {
|
||||
"darwin-x86_64": {
|
||||
"signature": $darwin_sig,
|
||||
"url": $darwin_url
|
||||
},
|
||||
"darwin-aarch64": {
|
||||
"signature": $darwin_sig,
|
||||
"url": $darwin_url
|
||||
},
|
||||
"windows-x86_64": {
|
||||
"signature": $windows_sig,
|
||||
"url": $windows_url
|
||||
}
|
||||
}
|
||||
}' > last_update.json
|
||||
cat last_update.json
|
||||
|
||||
- name: Generate the download static endpoint
|
||||
run: |
|
||||
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
||||
jq --null-input \
|
||||
--arg version "${VERSION}" \
|
||||
--arg pub_date "${PUB_DATE}" \
|
||||
--arg notes "${NOTES}" \
|
||||
--arg darwin_url "$RELEASE_DIR/dmg/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_universal.dmg" \
|
||||
--arg windows_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi" \
|
||||
'{
|
||||
"version": $version,
|
||||
"pub_date": $pub_date,
|
||||
"notes": $notes,
|
||||
"platforms": {
|
||||
"dmg-universal": {
|
||||
"url": $darwin_url
|
||||
},
|
||||
"msi-x86_64": {
|
||||
"url": $windows_url
|
||||
}
|
||||
}
|
||||
}' > last_download.json
|
||||
cat last_download.json
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
uses: 'google-github-actions/auth@v2.1.3'
|
||||
with:
|
||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||
|
||||
- name: Set up Google Cloud SDK
|
||||
uses: google-github-actions/setup-gcloud@v2.1.0
|
||||
with:
|
||||
project_id: kittycadapi
|
||||
|
||||
- name: Upload release files to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
with:
|
||||
path: artifact
|
||||
glob: '*/Zoo*'
|
||||
parent: false
|
||||
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
||||
|
||||
- name: Upload update endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
with:
|
||||
path: last_update.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload download endpoint to public bucket
|
||||
uses: google-github-actions/upload-cloud-storage@v2.1.1
|
||||
with:
|
||||
path: last_download.json
|
||||
destination: ${{ env.BUCKET_DIR }}
|
||||
|
||||
- name: Upload release files to Github
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: 'artifact/*/Zoo*'
|
||||
|
||||
announce_release:
|
||||
needs: [publish-apps-release]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'release'
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
- name: Announce Release
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
RELEASE_VERSION: ${{ github.event.release.tag_name }}
|
||||
RELEASE_BODY: ${{ github.event.release.body}}
|
||||
run: python public/announce_release.py
|
31
.github/workflows/label-issues.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: Label Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
permissions:
|
||||
issues: write
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check if issue opener is ZooSpiritWolf
|
||||
id: check_opener
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issueOpener = context.payload.issue.user.login;
|
||||
return issueOpener === 'ZooSpiritWolf';
|
||||
|
||||
- name: Add labels
|
||||
if: steps.check_opener.outputs.result == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.issue.number,
|
||||
labels: ['bug', 'regression', 'high-priority']
|
||||
});
|
153
.github/workflows/playwright.yml
vendored
@ -6,7 +6,7 @@ on:
|
||||
branches: [ main ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
@ -33,14 +33,15 @@ jobs:
|
||||
rust:
|
||||
- 'src/wasm-lib/**'
|
||||
|
||||
playwright-ubuntu:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
playwright-chrome:
|
||||
timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: check-rust-changes
|
||||
steps:
|
||||
- name: Tune GitHub-hosted runner network
|
||||
@ -52,6 +53,7 @@ jobs:
|
||||
cache: 'yarn'
|
||||
- uses: KittyCAD/action-install-cli@main
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: yarn
|
||||
- name: Cache Playwright Browsers
|
||||
uses: actions/cache@v4
|
||||
@ -60,6 +62,7 @@ jobs:
|
||||
~/.cache/ms-playwright/
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
|
||||
- name: Install Playwright Browsers
|
||||
shell: bash
|
||||
run: yarn playwright install --with-deps
|
||||
- name: Download Wasm Cache
|
||||
id: download-wasm
|
||||
@ -74,6 +77,7 @@ jobs:
|
||||
path: src/wasm-lib/pkg
|
||||
- name: copy wasm blob
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||
shell: bash
|
||||
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
continue-on-error: true
|
||||
- name: Setup Rust
|
||||
@ -88,7 +92,15 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: install good sed
|
||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
||||
shell: bash
|
||||
run: |
|
||||
brew install gnu-sed
|
||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Install vector
|
||||
shell: bash
|
||||
if: ${{ !startsWith(matrix.os, 'windows') }}
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
chmod +x /tmp/vector.sh
|
||||
@ -104,31 +116,39 @@ jobs:
|
||||
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
|
||||
- name: Build Wasm (because rust diff)
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: OR Build Wasm (because wasm cache failed)
|
||||
if: steps.download-wasm.outcome == 'failure'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: build web
|
||||
run: yarn build:local
|
||||
shell: bash
|
||||
- name: Run ubuntu/chrome snapshots
|
||||
shell: bash
|
||||
run: |
|
||||
yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
env:
|
||||
CI: true
|
||||
NODE_ENV: development
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: playwright-report-ubuntu-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: playwright-report-${{ matrix.os }}-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- name: Clean up test-results
|
||||
if: always()
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
run: rm -r test-results
|
||||
- name: check for changes
|
||||
shell: bash
|
||||
id: git-check
|
||||
run: |
|
||||
git add .
|
||||
@ -138,6 +158,7 @@ jobs:
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
git add .
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
@ -146,32 +167,31 @@ jobs:
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
# TODO when webkit works on ubuntu remove the os part of the commit message
|
||||
git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" || true
|
||||
git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
# only upload artifacts if there's actually changes
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
with:
|
||||
name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
# if have previous run results, use them
|
||||
- uses: actions/download-artifact@v4
|
||||
if: always()
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run ubuntu/chrome flow (with retries)
|
||||
- name: Run playwright/chrome flow (with retries)
|
||||
id: retry
|
||||
if: always()
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true
|
||||
yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert="@snapshot|@electron" || true
|
||||
# # send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
fi
|
||||
@ -186,7 +206,7 @@ jobs:
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --last-failed --grep-invert=@snapshot || true
|
||||
yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --last-failed --grep-invert="@snapshot|@electron" || true
|
||||
# send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
retry=$((retry + 1))
|
||||
@ -212,6 +232,10 @@ jobs:
|
||||
exit 0
|
||||
env:
|
||||
CI: true
|
||||
FAIL_ON_CONSOLE_ERRORS: true
|
||||
NODE_ENV: development
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- name: send to axiom
|
||||
if: always()
|
||||
@ -221,26 +245,26 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
playwright-macos:
|
||||
timeout-minutes: 30
|
||||
runs-on: macos-14
|
||||
|
||||
playwright-electron:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
os: [ubuntu-latest, windows-latest, macos-14]
|
||||
timeout-minutes: 40
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: check-rust-changes
|
||||
steps:
|
||||
- name: Tune GitHub-hosted runner network
|
||||
@ -250,18 +274,19 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- uses: KittyCAD/action-install-cli@main
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: yarn
|
||||
- name: Cache Playwright Browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-
|
||||
~/.cache/ms-playwright/
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
|
||||
- name: Install Playwright Browsers
|
||||
run: yarn playwright install --with-deps
|
||||
shell: bash
|
||||
run: yarn playwright install chromium --with-deps
|
||||
- name: Download Wasm Cache
|
||||
id: download-wasm
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||
@ -275,6 +300,7 @@ jobs:
|
||||
path: src/wasm-lib/pkg
|
||||
- name: copy wasm blob
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||
shell: bash
|
||||
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
continue-on-error: true
|
||||
- name: Setup Rust
|
||||
@ -289,49 +315,64 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- name: install good sed
|
||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
||||
shell: bash
|
||||
run: |
|
||||
brew install gnu-sed
|
||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||
- name: Install vector
|
||||
if: ${{ !startsWith(matrix.os, 'windows') }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||
chmod +x /tmp/vector.sh
|
||||
/tmp/vector.sh -y -no-modify-path
|
||||
mkdir -p /tmp/vector
|
||||
cp .github/workflows/vector.toml /tmp/vector.toml
|
||||
sed -i "" "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
|
||||
sed -i "" "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
|
||||
sed -i "" "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
|
||||
sed -i "" "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
|
||||
sed -i "" "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
|
||||
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
|
||||
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
|
||||
cat /tmp/vector.toml
|
||||
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
|
||||
- name: Build Wasm (because rust diff)
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: OR Build Wasm (because wasm cache failed)
|
||||
if: steps.download-wasm.outcome == 'failure'
|
||||
shell: bash
|
||||
run: yarn build:wasm
|
||||
- name: build web
|
||||
run: yarn build:local
|
||||
# if have previous run results, use them
|
||||
- name: build electron
|
||||
shell: bash
|
||||
run: yarn tron:package
|
||||
- uses: actions/download-artifact@v4
|
||||
if: ${{ always() }}
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-macos-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: test-results-${{ matrix.os }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
- name: Run macos/safari flow (with retries)
|
||||
- name: Run electron tests (with retries)
|
||||
id: retry
|
||||
if: always()
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
# if no last run artifact, than run plawright normally
|
||||
echo "run playwright normally"
|
||||
yarn playwright test --project="webkit" --config=playwright.ci.config.ts --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true
|
||||
if [[ "$IS_UBUNTU" == "true" ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn playwright test --config=playwright.electron.config.ts --grep=@electron || true
|
||||
else
|
||||
yarn playwright test --config=playwright.electron.config.ts --grep=@electron || true
|
||||
fi
|
||||
# # send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
retry=1
|
||||
max_retrys=4
|
||||
max_retrys=2
|
||||
|
||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||
while [[ $retry -le $max_retrys ]]; do
|
||||
@ -340,7 +381,11 @@ jobs:
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
yarn playwright test --project="webkit" --config=playwright.ci.config.ts --last-failed --grep-invert=@snapshot || true
|
||||
if [[ "$IS_UBUNTU" == "true" ]]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn playwright test --config=playwright.electron.config.ts --last-failed --grep=@electron || true
|
||||
else
|
||||
yarn playwright test --config=playwright.electron.config.ts --grep=@electron || true
|
||||
fi
|
||||
# send to axiom
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
|
||||
retry=$((retry + 1))
|
||||
@ -366,18 +411,28 @@ jobs:
|
||||
exit 0
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
FAIL_ON_CONSOLE_ERRORS: true
|
||||
NODE_ENV: development
|
||||
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
VITE_KC_SKIP_AUTH: true
|
||||
IS_UBUNTU: ${{ startsWith(matrix.os, 'ubuntu') && 'true' || 'false' }}
|
||||
#DEBUG: 'pw:browser*'
|
||||
- name: send to axiom
|
||||
if: ${{ !cancelled() && (success() || failure()) && !startsWith(matrix.os, 'windows') }}
|
||||
shell: bash
|
||||
run: |
|
||||
node playwrightProcess.mjs | tee /tmp/github-actions.log
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ always() }}
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: test-results-macos-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: test-results-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ always() }}
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: playwright-report-macos-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
6
.gitignore
vendored
@ -54,11 +54,15 @@ e2e/playwright/export-snapshots/*
|
||||
|
||||
## generated files
|
||||
src/**/*.typegen.ts
|
||||
src-tauri/gen
|
||||
|
||||
src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||
Mac_App_Distribution.provisionprofile
|
||||
|
||||
*.tsbuildinfo
|
||||
src/wasm-lib/pkg
|
||||
|
||||
venv
|
||||
.vite/
|
||||
|
||||
# electron
|
||||
out/
|
||||
|
@ -2,38 +2,6 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>NSDesktopFolderUsageDescription</key>
|
||||
<string>Zoo Modeling App accesses the Desktop to load and save your project files and/or exported files here</string>
|
||||
<key>NSDocumentsFolderUsageDescription</key>
|
||||
<string>Zoo Modeling App accesses the Documents folder to load and save your project files and/or exported files here</string>
|
||||
<key>NSDownloadsFolderUsageDescription</key>
|
||||
<string>Zoo Modeling App accesses the Downloads folder to load and save your project files and/or exported files here</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>DTXcode</key>
|
||||
<string>1501</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>15A507</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>dev.zoo.modeling-app</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>zoo-modeling-app</string>
|
||||
<string>zoo</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSFileQuarantineEnabled</key>
|
||||
<false/>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
8
Makefile
@ -7,6 +7,14 @@ XSTATE_TYPEGENS := $(wildcard src/machines/*.typegen.ts)
|
||||
dev: node_modules public/wasm_lib_bg.wasm $(XSTATE_TYPEGENS)
|
||||
yarn start
|
||||
|
||||
# I'm sorry this is so specific to my setup you may as well ignore this.
|
||||
# This is so you don't have to deal with electron windows popping up constantly.
|
||||
# It should work for you other Linux users.
|
||||
lee-electron-test:
|
||||
Xephyr -br -ac -noreset -screen 1200x500 :2 &
|
||||
DISPLAY=:2 NODE_ENV=development PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn tron:test -g "when using the file tree"
|
||||
killall Xephyr
|
||||
|
||||
$(XSTATE_TYPEGENS): $(TS_SRC)
|
||||
yarn xstate typegen 'src/**/*.ts?(x)'
|
||||
|
||||
|
69
README.md
@ -89,26 +89,19 @@ enable third-party cookies. You can enable third-party cookies by clicking on
|
||||
the eye with a slash through it in the URL bar, and clicking on "Enable
|
||||
Third-Party Cookies".
|
||||
|
||||
## Tauri
|
||||
## Desktop
|
||||
|
||||
To spin up up tauri dev, `yarn install` and `yarn build:wasm-dev` need to have been done before hand then
|
||||
To spin up the desktop app, `yarn install` and `yarn build:wasm-dev` need to have been done before hand then
|
||||
|
||||
```
|
||||
yarn tauri dev
|
||||
yarn electron:start
|
||||
```
|
||||
|
||||
Will spin up the web app before opening up the tauri dev desktop app. Note that it's probably a good idea to close the browser tab that gets opened since at the time of writing they can conflict.
|
||||
This will start the application and hot-reload on changed.
|
||||
|
||||
The dev instance automatically opens up the browser devtools which can be disabled by [commenting it out](https://github.com/KittyCAD/modeling-app/blob/main/src-tauri/src/main.rs#L92.)
|
||||
Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
|
||||
|
||||
To build, run `yarn tauri build`, or `yarn tauri build --debug` to keep access to the devtools.
|
||||
|
||||
Note that these became separate apps on Macos, so make sure you open the right one after a build 😉
|
||||

|
||||
|
||||
<img width="1232" alt="image" src="https://user-images.githubusercontent.com/29681384/211947063-46164bb4-7bdd-45cb-9a76-2f40c71a24aa.png">
|
||||
|
||||
<img width="1232" alt="image (1)" src="https://user-images.githubusercontent.com/29681384/211947073-e76b4933-bef5-4636-bc4d-e930ac8e290f.png">
|
||||
To build, run `yarn tron:package`.
|
||||
|
||||
## Checking out commits / Bisecting
|
||||
|
||||
@ -117,7 +110,6 @@ 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 wasm-prep
|
||||
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
|
||||
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
||||
```
|
||||
@ -196,12 +188,22 @@ For more information on fuzzing you can check out
|
||||
|
||||
### Playwright tests
|
||||
|
||||
You will need a `./e2e/playwright/playwright-secrets.env` file:
|
||||
|
||||
```bash
|
||||
$ touch ./e2e/playwright/playwright-secrets.env
|
||||
$ cat ./e2e/playwright/playwright-secrets.env
|
||||
token=<dev.zoo.dev/account/api-tokens>
|
||||
snapshottoken=<your-snapshot-token>
|
||||
```
|
||||
|
||||
For a portable way to run Playwright you'll need Docker.
|
||||
|
||||
#### Generic example
|
||||
After that, open a terminal and run:
|
||||
|
||||
```bash
|
||||
docker run --network host --rm --init -it playwright/chrome:playwright-1.43.1
|
||||
docker run --network host --rm --init -it playwright/chrome:playwright-x.xx.x
|
||||
```
|
||||
|
||||
and in another terminal, run:
|
||||
@ -210,21 +212,27 @@ and in another terminal, run:
|
||||
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" <test suite>
|
||||
```
|
||||
|
||||
An example of a `<test suite>` is: `e2e/playwright/flow-tests.spec.ts`
|
||||
|
||||
YOU WILL NEED A PLAYWRIGHT-SECRETS.ENV FILE:
|
||||
#### Specific example
|
||||
|
||||
open a terminal and run:
|
||||
|
||||
```bash
|
||||
# ./e2e/playwright/playwright-secrets.env
|
||||
token=<your-token>
|
||||
snapshottoken=<your-snapshot-token>
|
||||
docker run --network host --rm --init -it playwright/chrome:playwright-1.46.0
|
||||
```
|
||||
|
||||
and in another terminal, run:
|
||||
|
||||
```bash
|
||||
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" e2e/playwright/command-bar-tests.spec.ts
|
||||
```
|
||||
then replace "your-token" with a dev token from dev.zoo.dev/account/api-tokens
|
||||
|
||||
run a specific test change the test from `test('...` to `test.only('...`
|
||||
(note if you commit this, the tests will instantly fail without running any of the tests)
|
||||
|
||||
|
||||
**Gotcha**: running the docker container with a mismatched image against your `./node_modules/playwright` will cause a failure. Make sure the versions are matched and up to date.
|
||||
|
||||
run headed
|
||||
|
||||
```
|
||||
@ -343,25 +351,6 @@ PS: for the debug panel, the following JSON is useful for snapping the camera
|
||||
|
||||
</details>
|
||||
|
||||
### Tauri e2e tests
|
||||
|
||||
#### Windows (local only until the CI edge version mismatch is fixed)
|
||||
|
||||
```
|
||||
yarn install
|
||||
yarn build:wasm-dev
|
||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||
yarn vite build --mode development
|
||||
yarn tauri build --debug -b
|
||||
$env:KITTYCAD_API_TOKEN="<YOUR_KITTYCAD_API_TOKEN>"
|
||||
$env:VITE_KC_API_BASE_URL="https://api.dev.zoo.dev"
|
||||
$env:E2E_TAURI_ENABLED="true"
|
||||
$env:TS_NODE_COMPILER_OPTIONS='{"module": "commonjs"}'
|
||||
$env:E2E_APPLICATION=".\src-tauri\target\debug\Zoo Modeling App.exe"
|
||||
Stop-Process -Name msedgedriver
|
||||
yarn wdio run wdio.conf.ts
|
||||
```
|
||||
|
||||
## KCL
|
||||
|
||||
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
||||
|
BIN
assets/icon.icns
Normal file
BIN
assets/icon.ico
Normal file
After Width: | Height: | Size: 183 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
BIN
assets/icon@2x.icns
Normal file
@ -22,8 +22,3 @@ once fixed in engine will just start working here with no language changes.
|
||||
|
||||
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||
chamfer cases work currently.
|
||||
|
||||
Sketching on the chamfered face does not currently work.
|
||||
|
||||
- **Shell**: Shell sometimes does not work when arcs or fillets are involved.
|
||||
We are tracking the engine side bug on this.
|
||||
|
@ -236,7 +236,7 @@ const extrusion = extrude(5, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -254,7 +254,7 @@ const extrusion = extrude(5, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -445,7 +445,7 @@ const extrusion = extrude(5, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -463,7 +463,7 @@ const extrusion = extrude(5, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -240,7 +240,7 @@ const extrusion = extrude(5, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -258,7 +258,7 @@ const extrusion = extrude(5, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -449,7 +449,7 @@ const extrusion = extrude(5, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -467,7 +467,7 @@ const extrusion = extrude(5, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -155,7 +155,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -173,7 +173,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -364,7 +364,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -382,7 +382,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -575,7 +575,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -593,7 +593,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -784,7 +784,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -802,7 +802,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -154,7 +154,7 @@ const extrusion = extrude(10, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -172,7 +172,7 @@ const extrusion = extrude(10, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -363,7 +363,7 @@ const extrusion = extrude(10, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -381,7 +381,7 @@ const extrusion = extrude(10, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -574,7 +574,7 @@ const extrusion = extrude(10, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -592,7 +592,7 @@ const extrusion = extrude(10, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -783,7 +783,7 @@ const extrusion = extrude(10, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -801,7 +801,7 @@ const extrusion = extrude(10, sketch001)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -156,7 +156,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -174,7 +174,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -365,7 +365,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -383,7 +383,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -576,7 +576,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -594,7 +594,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -785,7 +785,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -803,7 +803,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -248,7 +248,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -266,7 +266,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -457,7 +457,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -475,7 +475,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -668,7 +668,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -686,7 +686,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -877,7 +877,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -895,7 +895,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -153,7 +153,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -171,7 +171,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -362,7 +362,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -380,7 +380,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -573,7 +573,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -591,7 +591,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -782,7 +782,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -800,7 +800,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -153,7 +153,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -171,7 +171,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -362,7 +362,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -380,7 +380,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -573,7 +573,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -591,7 +591,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -782,7 +782,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -800,7 +800,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -166,7 +166,7 @@ const exampleSketch = startSketchOn('XZ')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -184,7 +184,7 @@ const exampleSketch = startSketchOn('XZ')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -375,7 +375,7 @@ const exampleSketch = startSketchOn('XZ')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -393,7 +393,7 @@ const exampleSketch = startSketchOn('XZ')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -586,7 +586,7 @@ const exampleSketch = startSketchOn('XZ')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -604,7 +604,7 @@ const exampleSketch = startSketchOn('XZ')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -795,7 +795,7 @@ const exampleSketch = startSketchOn('XZ')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -813,7 +813,7 @@ const exampleSketch = startSketchOn('XZ')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
858
docs/kcl/arrayReduce.md
Normal file
@ -159,7 +159,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -177,7 +177,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -368,7 +368,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -386,7 +386,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -579,7 +579,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -597,7 +597,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -788,7 +788,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -806,7 +806,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -154,7 +154,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -172,7 +172,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -363,7 +363,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -381,7 +381,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -574,7 +574,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -592,7 +592,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -783,7 +783,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -801,7 +801,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
38
docs/kcl/cm.md
Normal file
38
docs/kcl/ft.md
Normal file
861
docs/kcl/hollow.md
Normal file
38
docs/kcl/inch.md
Normal file
@ -19,6 +19,7 @@ layout: manual
|
||||
* [`angledLineToX`](kcl/angledLineToX)
|
||||
* [`angledLineToY`](kcl/angledLineToY)
|
||||
* [`arc`](kcl/arc)
|
||||
* [`arrayReduce`](kcl/arrayReduce)
|
||||
* [`asin`](kcl/asin)
|
||||
* [`assert`](kcl/assert)
|
||||
* [`assertEqual`](kcl/assertEqual)
|
||||
@ -32,17 +33,21 @@ layout: manual
|
||||
* [`chamfer`](kcl/chamfer)
|
||||
* [`circle`](kcl/circle)
|
||||
* [`close`](kcl/close)
|
||||
* [`cm`](kcl/cm)
|
||||
* [`cos`](kcl/cos)
|
||||
* [`e`](kcl/e)
|
||||
* [`extrude`](kcl/extrude)
|
||||
* [`fillet`](kcl/fillet)
|
||||
* [`floor`](kcl/floor)
|
||||
* [`ft`](kcl/ft)
|
||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||
* [`helix`](kcl/helix)
|
||||
* [`hole`](kcl/hole)
|
||||
* [`hollow`](kcl/hollow)
|
||||
* [`import`](kcl/import)
|
||||
* [`inch`](kcl/inch)
|
||||
* [`int`](kcl/int)
|
||||
* [`lastSegX`](kcl/lastSegX)
|
||||
* [`lastSegY`](kcl/lastSegY)
|
||||
@ -52,11 +57,15 @@ layout: manual
|
||||
* [`line`](kcl/line)
|
||||
* [`lineTo`](kcl/lineTo)
|
||||
* [`ln`](kcl/ln)
|
||||
* [`loft`](kcl/loft)
|
||||
* [`log`](kcl/log)
|
||||
* [`log10`](kcl/log10)
|
||||
* [`log2`](kcl/log2)
|
||||
* [`m`](kcl/m)
|
||||
* [`max`](kcl/max)
|
||||
* [`min`](kcl/min)
|
||||
* [`mm`](kcl/mm)
|
||||
* [`offsetPlane`](kcl/offsetPlane)
|
||||
* [`patternCircular2d`](kcl/patternCircular2d)
|
||||
* [`patternCircular3d`](kcl/patternCircular3d)
|
||||
* [`patternLinear2d`](kcl/patternLinear2d)
|
||||
@ -82,6 +91,7 @@ layout: manual
|
||||
* [`tan`](kcl/tan)
|
||||
* [`tangentialArc`](kcl/tangentialArc)
|
||||
* [`tangentialArcTo`](kcl/tangentialArcTo)
|
||||
* [`tangentialArcToRelative`](kcl/tangentialArcToRelative)
|
||||
* [`tau`](kcl/tau)
|
||||
* [`toDegrees`](kcl/toDegrees)
|
||||
* [`toRadians`](kcl/toRadians)
|
||||
@ -89,3 +99,4 @@ layout: manual
|
||||
* [`xLineTo`](kcl/xLineTo)
|
||||
* [`yLine`](kcl/yLine)
|
||||
* [`yLineTo`](kcl/yLineTo)
|
||||
* [`yd`](kcl/yd)
|
||||
|
@ -145,7 +145,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -163,7 +163,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -354,7 +354,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -372,7 +372,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -145,7 +145,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -163,7 +163,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -354,7 +354,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -372,7 +372,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -158,7 +158,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -176,7 +176,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -367,7 +367,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -385,7 +385,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -578,7 +578,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -596,7 +596,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -787,7 +787,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -805,7 +805,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -145,7 +145,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -163,7 +163,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -354,7 +354,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -372,7 +372,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -565,7 +565,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -583,7 +583,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -774,7 +774,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -792,7 +792,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
516
docs/kcl/loft.md
Normal file
38
docs/kcl/m.md
Normal file
38
docs/kcl/mm.md
Normal file
138
docs/kcl/offsetPlane.md
Normal file
@ -45,7 +45,7 @@ const example = extrude(1, exampleSketch)
|
||||
// The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once.
|
||||
repetitions: number,
|
||||
// Whether or not to rotate the duplicates as they are copied.
|
||||
rotateDuplicates: string,
|
||||
rotateDuplicates: bool,
|
||||
}
|
||||
```
|
||||
* `sketch_group_set`: `SketchGroupSet` - A sketch group or a group of sketch groups. (REQUIRED)
|
||||
@ -163,7 +163,7 @@ const example = extrude(1, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -181,7 +181,7 @@ const example = extrude(1, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -373,7 +373,7 @@ const example = extrude(1, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -391,7 +391,7 @@ const example = extrude(1, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -285,7 +285,7 @@ const example = extrude(1, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -303,7 +303,7 @@ const example = extrude(1, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -287,7 +287,7 @@ let vase = layer()
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -305,7 +305,7 @@ let vase = layer()
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -146,7 +146,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -164,7 +164,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -355,7 +355,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -373,7 +373,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -141,7 +141,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -159,7 +159,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -350,7 +350,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -368,7 +368,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -140,7 +140,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -158,7 +158,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -349,7 +349,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -367,7 +367,7 @@ const sketch001 = startSketchOn('XY')
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -224,7 +224,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -242,7 +242,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -527,7 +527,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -545,7 +545,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -736,7 +736,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -754,7 +754,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -171,7 +171,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -189,7 +189,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -380,7 +380,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -398,7 +398,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
35111
docs/kcl/std.json
863
docs/kcl/tangentialArcToRelative.md
Normal file
@ -13,14 +13,16 @@ arrays can hold objects and vice versa.
|
||||
|
||||
`true` or `false` work when defining values.
|
||||
|
||||
## Variable declaration
|
||||
## Constant declaration
|
||||
|
||||
Variables are defined with the `let` keyword like so:
|
||||
Constants are defined with the `let` keyword like so:
|
||||
|
||||
```
|
||||
let myBool = false
|
||||
```
|
||||
|
||||
Currently you cannot redeclare a constant.
|
||||
|
||||
## Array
|
||||
|
||||
An array is defined with `[]` braces. What is inside the brackets can
|
||||
|
@ -148,7 +148,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -166,7 +166,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -357,7 +357,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -375,7 +375,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -568,7 +568,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -586,7 +586,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -777,7 +777,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -795,7 +795,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -148,7 +148,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -166,7 +166,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -357,7 +357,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -375,7 +375,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -568,7 +568,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -586,7 +586,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -777,7 +777,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -795,7 +795,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -146,7 +146,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -164,7 +164,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -355,7 +355,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -373,7 +373,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -566,7 +566,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -584,7 +584,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -775,7 +775,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -793,7 +793,7 @@ const example = extrude(10, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
@ -144,7 +144,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -162,7 +162,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -353,7 +353,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -371,7 +371,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -564,7 +564,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -582,7 +582,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -773,7 +773,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
@ -791,7 +791,7 @@ const example = extrude(5, exampleSketch)
|
||||
} |
|
||||
{
|
||||
// arc's direction
|
||||
ccw: string,
|
||||
ccw: bool,
|
||||
// the arc's center
|
||||
center: [number, number],
|
||||
// The from point.
|
||||
|
38
docs/kcl/yd.md
Normal file
66
e2e/playwright/app-header-tests.spec.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { setupElectron, tearDown } from './test-utils'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Electron app header tests', () => {
|
||||
test(
|
||||
'Open Command Palette button has correct shortcut',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// No space before the shortcut since it checks textContent.
|
||||
let text
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
text = 'Commands⌘K'
|
||||
break
|
||||
case 'win32':
|
||||
text = 'CommandsCtrl+K'
|
||||
break
|
||||
default: // 'linux' etc.
|
||||
text = 'CommandsCtrl+K'
|
||||
break
|
||||
}
|
||||
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandsButton).toBeVisible()
|
||||
await expect(commandsButton).toHaveText(text)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'User settings has correct shortcut',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// Open the user sidebar menu.
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
|
||||
// No space after "User settings" since it's textContent.
|
||||
const text =
|
||||
process.platform === 'darwin' ? 'User settings⌘,' : 'User settingsCtrl,'
|
||||
const userSettingsButton = page.getByTestId('user-settings')
|
||||
await expect(userSettingsButton).toBeVisible()
|
||||
await expect(userSettingsButton).toHaveText(text)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
@ -8,8 +8,8 @@ import {
|
||||
PERSIST_MODELING_CONTEXT,
|
||||
} from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
@ -84,6 +84,7 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
} else {
|
||||
await page.waitForTimeout(500)
|
||||
}
|
||||
await page.waitForTimeout(200)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator)
|
||||
@ -95,32 +96,49 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
}
|
||||
|
||||
// deselect line tool
|
||||
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||
await page.waitForTimeout(500)
|
||||
const btnLine = page.getByTestId('line')
|
||||
const btnLineAriaPressed = await btnLine.getAttribute('aria-pressed')
|
||||
if (btnLineAriaPressed === 'true') {
|
||||
await btnLine.click()
|
||||
}
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
|
||||
if (openPanes.includes('code')) {
|
||||
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.WHITE)).toBeLessThan(3)
|
||||
await expect(
|
||||
await u.getGreatestPixDiff(line1, [249, 249, 249])
|
||||
).toBeLessThan(3)
|
||||
await expect
|
||||
.poll(async () => u.getGreatestPixDiff(line1, TEST_COLORS.WHITE))
|
||||
.toBeLessThan(3)
|
||||
await page.waitForTimeout(100)
|
||||
await expect
|
||||
.poll(async () => u.getGreatestPixDiff(line1, [249, 249, 249]))
|
||||
.toBeLessThan(3)
|
||||
await page.waitForTimeout(100)
|
||||
}
|
||||
|
||||
// click between first two clicks to get center of the line
|
||||
await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
|
||||
await expect(
|
||||
await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)
|
||||
).toBeLessThan(3)
|
||||
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
|
||||
}
|
||||
|
||||
// hold down shift
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click between the latest two clicks to get center of the line
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 20)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// selected two lines therefore there should be two cursors
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||
await page.waitForTimeout(100)
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Length: open menu' }).click()
|
||||
@ -137,6 +155,8 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
||||
|
||||
test.describe('Basic sketch', () => {
|
||||
test('code pane open at start', async ({ page }) => {
|
||||
// Skip on windows it is being weird.
|
||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||
await doBasicSketch(page, ['code'])
|
||||
})
|
||||
|
||||
|
@ -3,8 +3,8 @@ import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
@ -61,7 +61,7 @@ test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
await page.waitForTimeout(300) // wait for animation
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Line', exact: true })
|
||||
page.getByRole('button', { name: 'line Line', exact: true })
|
||||
).toBeVisible()
|
||||
|
||||
// draw a line
|
||||
@ -72,7 +72,10 @@ test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
|
||||
await page.getByRole('button', { name: 'Line', exact: true }).click()
|
||||
await page
|
||||
.getByRole('button', { name: 'line Line', exact: true })
|
||||
.first()
|
||||
.click()
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
@ -1,11 +1,19 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import { join } from 'path'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
@ -19,9 +27,19 @@ test.describe('Code pane and errors', () => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
await page.addInitScript((code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`// Extruded Triangle
|
||||
const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([-5, 10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(5, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
@ -47,6 +65,8 @@ test.describe('Code pane and errors', () => {
|
||||
test('Opening and closing the code pane will consistently show error diagnostics', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('http://localhost:3000')
|
||||
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Load the app with the working starter code
|
||||
@ -72,7 +92,7 @@ test.describe('Code pane and errors', () => {
|
||||
|
||||
// Delete a character to break the KCL
|
||||
await u.openKclCodePanel()
|
||||
await page.getByText('extrude(').click()
|
||||
await page.getByText('thickness, bracketLeg1Sketch)').click()
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
// Ensure that a badge appears on the button
|
||||
@ -83,7 +103,7 @@ test.describe('Code pane and errors', () => {
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||
await expect(page.locator('.cm-tooltip').first()).toBeVisible()
|
||||
|
||||
// Close the code pane
|
||||
await codePaneButton.click()
|
||||
@ -106,7 +126,7 @@ test.describe('Code pane and errors', () => {
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||
await expect(page.locator('.cm-tooltip').first()).toBeVisible()
|
||||
})
|
||||
|
||||
test('When error is not in view you can click the badge to scroll to it', async ({
|
||||
@ -217,3 +237,79 @@ test.describe('Code pane and errors', () => {
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test(
|
||||
'Opening multiple panes persists when switching projects',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
// Setup multiple projects.
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([
|
||||
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||
fsp.mkdir(bracketDir, { recursive: true }),
|
||||
])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await test.step('Opening the bracket project should load', async () => {
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
// If they're open by default, we're not actually testing anything.
|
||||
await test.step('Pre-condition: panes are not already visible', async () => {
|
||||
await expect(page.locator('#variables-pane')).not.toBeVisible()
|
||||
await expect(page.locator('#logs-pane')).not.toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Open multiple panes', async () => {
|
||||
await u.openKclCodePanel()
|
||||
await u.openVariablesPane()
|
||||
await u.openLogsPane()
|
||||
})
|
||||
|
||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||
await page.getByTestId('app-logo').click()
|
||||
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Opening the router-template project should load', async () => {
|
||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||
|
||||
await page.getByText('router-template-slate').click()
|
||||
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
await test.step('All panes opened before should be visible', async () => {
|
||||
await expect(page.locator('#code-pane')).toBeVisible()
|
||||
await expect(page.locator('#variables-pane')).toBeVisible()
|
||||
await expect(page.locator('#logs-pane')).toBeVisible()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
@ -3,8 +3,8 @@ import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
@ -42,11 +42,11 @@ test.describe('Command bar tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.waitForTimeout(200)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await page.waitForTimeout(200)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await page.waitForTimeout(200)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
|
||||
)
|
||||
@ -124,7 +124,7 @@ const extrude001 = extrude(-10, sketch001)`
|
||||
await expect(cmdSearchBar).not.toBeVisible()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
await page.keyboard.press('ControlOrMeta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
@ -185,7 +185,7 @@ const extrude001 = extrude(-10, sketch001)`
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
await page.keyboard.press('ControlOrMeta+K')
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
@ -250,7 +250,7 @@ const extrude001 = extrude(-10, sketch001)`
|
||||
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await page.keyboard.press('Meta+K')
|
||||
await page.keyboard.press('ControlOrMeta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
// Search for extrude command and choose it
|
||||
@ -321,20 +321,18 @@ const extrude001 = extrude(distance001, sketch001)`.replace(
|
||||
name: 'rectangle',
|
||||
})
|
||||
const rectangleToolButton = page.getByRole('button', {
|
||||
name: 'Corner rectangle',
|
||||
exact: true,
|
||||
name: 'rectangle Corner rectangle',
|
||||
})
|
||||
const lineToolCommand = page.getByRole('option', {
|
||||
name: 'Line',
|
||||
})
|
||||
const lineToolButton = page.getByRole('button', {
|
||||
name: 'Line',
|
||||
name: 'line Line',
|
||||
exact: true,
|
||||
})
|
||||
const arcToolCommand = page.getByRole('option', { name: 'Tangential Arc' })
|
||||
const arcToolButton = page.getByRole('button', {
|
||||
name: 'Tangential Arc',
|
||||
exact: true,
|
||||
name: 'arc Tangential Arc',
|
||||
})
|
||||
|
||||
// Start a sketch
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
test.describe('Copilot ghost text', () => {
|
||||
// eslint-disable-next-line jest/valid-title
|
||||
test.skip(true, 'Needs to get covered again')
|
||||
|
||||
test('completes code in empty file', async ({ page }) => {
|
||||
@ -331,7 +332,6 @@ test.describe('Copilot ghost text', () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -348,10 +348,10 @@ test.describe('Copilot ghost text', () => {
|
||||
)
|
||||
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await page.keyboard.up('Shift')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
@ -367,8 +367,6 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
await page.waitForTimeout(800)
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -381,17 +379,17 @@ test.describe('Copilot ghost text', () => {
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
// Ctrl+z
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
// Ctrl+shift+z
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`)
|
||||
@ -410,14 +408,14 @@ test.describe('Copilot ghost text', () => {
|
||||
)
|
||||
|
||||
// Once for the enter.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
// Once for the text.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
|
191
e2e/playwright/desktop-export.spec.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { join } from 'path'
|
||||
import {
|
||||
getUtils,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test(
|
||||
'export works on the first try',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(bracketDir, 'other.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
await test.step('on open of project', async () => {
|
||||
await expect(page.getByText(`bracket`)).toBeVisible()
|
||||
|
||||
// open the project
|
||||
await page.getByText(`bracket`).click()
|
||||
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// export the model
|
||||
const exportButton = page.getByTestId('export-pane-button')
|
||||
await expect(exportButton).toBeVisible()
|
||||
|
||||
// Wait for the model to finish loading
|
||||
const modelStateIndicator = page.getByTestId(
|
||||
'model-state-indicator-execution-done'
|
||||
)
|
||||
await expect(modelStateIndicator).toBeVisible({ timeout: 60000 })
|
||||
|
||||
const gltfOption = page.getByText('glTF')
|
||||
const submitButton = page.getByText('Confirm Export')
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||
|
||||
// Click the export button
|
||||
await exportButton.click()
|
||||
|
||||
await expect(gltfOption).toBeVisible()
|
||||
await expect(page.getByText('STL')).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Click the checkbox
|
||||
await expect(submitButton).toBeVisible()
|
||||
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
|
||||
await test.step('Check the export size', async () => {
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile('output.gltf')
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
return 0
|
||||
}
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBe(482669)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('on open of file in file pane', async () => {
|
||||
const u = await getUtils(page)
|
||||
await u.openFilePanel()
|
||||
|
||||
const otherKclButton = page.getByRole('button', { name: 'other.kcl' })
|
||||
|
||||
// Click the file
|
||||
await otherKclButton.click()
|
||||
|
||||
// Close the file pane
|
||||
await u.closeFilePanel()
|
||||
|
||||
// wait for it to finish executing (todo: make this more robust)
|
||||
await page.waitForTimeout(1000)
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// export the model
|
||||
const exportButton = page.getByTestId('export-pane-button')
|
||||
await expect(exportButton).toBeVisible()
|
||||
|
||||
const gltfOption = page.getByText('glTF')
|
||||
const submitButton = page.getByText('Confirm Export')
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||
|
||||
// Click the export button
|
||||
await exportButton.click()
|
||||
|
||||
await expect(gltfOption).toBeVisible()
|
||||
await expect(page.getByText('STL')).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Click the checkbox
|
||||
await expect(submitButton).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
|
||||
await test.step('Check the export size', async () => {
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile('output.gltf')
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
return 0
|
||||
}
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBe(105022)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
})
|
||||
await electronApp.close()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
@ -2,8 +2,8 @@ import { test, expect } from '@playwright/test'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
@ -16,7 +16,6 @@ test.describe('Editor tests', () => {
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
@ -29,9 +28,9 @@ test.describe('Editor tests', () => {
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||
@ -42,9 +41,9 @@ test.describe('Editor tests', () => {
|
||||
// |> close(%)`)
|
||||
|
||||
// uncomment the code
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||
@ -85,6 +84,63 @@ test.describe('Editor tests', () => {
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('if you click the format button it formats your code and executes so lints are still there', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type(`const sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-info')
|
||||
await expect(
|
||||
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||
).toBeVisible()
|
||||
|
||||
await page.locator('#code-pane button:first-child').click()
|
||||
await page.locator('button:has-text("Format code")').click()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-info')
|
||||
await expect(
|
||||
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('fold gutters work', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -148,9 +204,7 @@ test.describe('Editor tests', () => {
|
||||
// Delete all the code.
|
||||
await page.locator('.cm-content').click()
|
||||
// Select all
|
||||
await page.keyboard.press('Control+A')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Meta+A')
|
||||
await page.keyboard.press('ControlOrMeta+A')
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -244,6 +298,67 @@ test.describe('Editor tests', () => {
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
test('if you use the format keyboard binding it formats your code and executes so lints are shown', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-info')
|
||||
await expect(
|
||||
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||
).toBeVisible()
|
||||
|
||||
// focus the editor
|
||||
await u.codeLocator.click()
|
||||
|
||||
// Hit alt+shift+f to format the code
|
||||
await page.keyboard.press('Alt+Shift+KeyF')
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch_001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-info')
|
||||
await expect(
|
||||
page.getByText('Identifiers must be lowerCamelCase').first()
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('if you write kcl with lint errors you get lints', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
@ -354,7 +469,7 @@ test.describe('Editor tests', () => {
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token').first()).toBeVisible()
|
||||
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
|
||||
|
||||
// select the line that's causing the error and delete it
|
||||
await page.getByText('$ error').click()
|
||||
@ -402,7 +517,7 @@ test.describe('Editor tests', () => {
|
||||
const width = 0.500
|
||||
const height = 0.500
|
||||
const dia = 4
|
||||
|
||||
|
||||
fn squareHole = (l, w) => {
|
||||
const squareHoleSketch = startSketchOn('XY')
|
||||
|> startProfileAt([-width / 2, -length / 2], %)
|
||||
@ -682,10 +797,6 @@ test.describe('Editor tests', () => {
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [665, 458]
|
||||
|
||||
const dragPX = 40
|
||||
|
||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
@ -693,10 +804,10 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
await page.keyboard.press('Enter')
|
||||
await page.getByRole('button', { name: 'checkmark Submit command' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// expect the code to have changed
|
||||
@ -718,17 +829,15 @@ test.describe('Editor tests', () => {
|
||||
|> close(%)`)
|
||||
})
|
||||
|
||||
// failing for the same reason as "Can edit a sketch that has been extruded in the same pipe"
|
||||
// please fix together
|
||||
test.fixme('Can undo a sketch modification with ctrl+z', async ({ page }) => {
|
||||
test('Can undo a sketch modification with ctrl+z', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`
|
||||
)
|
||||
@ -763,11 +872,11 @@ test.describe('Editor tests', () => {
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [665, 458]
|
||||
const startPX = [665, 397]
|
||||
|
||||
const dragPX = 40
|
||||
|
||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||
await page.getByText('startProfileAt([4.61, -10.01], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
@ -805,7 +914,7 @@ test.describe('Editor tests', () => {
|
||||
// drag tangentialArcTo handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 },
|
||||
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
|
||||
targetPosition: {
|
||||
x: tangentEnd.x + dragPX,
|
||||
y: tangentEnd.y + dragPX,
|
||||
@ -817,12 +926,12 @@ test.describe('Editor tests', () => {
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -16.82], %)
|
||||
|> line([15.4, -2.74], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> line([2.65, -2.69], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([15.39, -2.78], %)
|
||||
|> tangentialArcTo([27.6, -3.05], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|
||||
// Hit undo
|
||||
await page.keyboard.down('Control')
|
||||
@ -831,11 +940,11 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -16.82], %)
|
||||
|> line([15.4, -2.74], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([15.39, -2.78], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
@ -844,11 +953,12 @@ test.describe('Editor tests', () => {
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.12, -16.82], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
|> startProfileAt([7.12, -12.68], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
`)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
@ -858,9 +968,9 @@ test.describe('Editor tests', () => {
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line([12.73, -0.09], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> tangentialArcTo([24.95, -0.38], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
})
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 71 KiB |
869
e2e/playwright/file-tree.spec.ts
Normal file
@ -0,0 +1,869 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import * as fsp from 'fs/promises'
|
||||
import * as fs from 'fs'
|
||||
import {
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
import { join } from 'path'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('when using the file tree to', () => {
|
||||
const fromFile = 'main.kcl'
|
||||
const toFile = 'hello.kcl'
|
||||
|
||||
test(
|
||||
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
renameFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
|
||||
// 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 renameFile(fromFile, toFile)
|
||||
await page.reload()
|
||||
|
||||
await test.step('Postcondition: editor has same content as before the rename', async () => {
|
||||
await editorTextMatches(kclCube)
|
||||
})
|
||||
|
||||
await test.step('Postcondition: opening and closing settings works', async () => {
|
||||
const settingsOpenButton = page.getByRole('link', {
|
||||
name: 'settings Settings',
|
||||
})
|
||||
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||
await settingsOpenButton.click()
|
||||
await settingsCloseButton.click()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`create many new untitled files they increment their names`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
const { panesOpen, createAndSelectProject, createNewFile } =
|
||||
await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
|
||||
await createNewFile('')
|
||||
await createNewFile('')
|
||||
await createNewFile('')
|
||||
await createNewFile('')
|
||||
await createNewFile('')
|
||||
|
||||
await test.step('Postcondition: there are 5 new Untitled-*.kcl files', async () => {
|
||||
await expect(
|
||||
page
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: /Untitled[-]?[0-5]?/ })
|
||||
).toHaveCount(5)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'create a new file with the same name as an existing file cancels the operation',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
})
|
||||
|
||||
const {
|
||||
openKclCodePanel,
|
||||
openFilePanel,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
createNewFileAndSelect,
|
||||
renameFile,
|
||||
selectFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
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)
|
||||
|
||||
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 editorTextMatches(kclCube)
|
||||
})
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
|
||||
await selectFile(kcl2)
|
||||
await editorTextMatches(kclCylinder)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'deleting all files recreates a default main.kcl with no code',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
deleteFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
// File the main.kcl with contents
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
const kcl1 = 'main.kcl'
|
||||
|
||||
await deleteFile(kcl1)
|
||||
|
||||
await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => {
|
||||
await editorTextMatches('')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'loading small file, then large, then back to small',
|
||||
{
|
||||
tag: '@electron',
|
||||
},
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { page } = await setupElectron({
|
||||
testInfo,
|
||||
})
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
createNewFile,
|
||||
openDebugPanel,
|
||||
closeDebugPanel,
|
||||
expectCmdLog,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
await createAndSelectProject('project-000')
|
||||
|
||||
// Create a small file
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
// pasted into main.kcl
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
// Create a large lego file
|
||||
await createNewFile('lego')
|
||||
const legoFile = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'lego.kcl' }),
|
||||
})
|
||||
await expect(legoFile).toBeVisible({ timeout: 60_000 })
|
||||
await legoFile.click()
|
||||
const kclLego = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/lego.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclLego)
|
||||
const mainFile = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'main.kcl' }),
|
||||
})
|
||||
|
||||
// Open settings and enable the debug panel
|
||||
await page
|
||||
.getByRole('link', {
|
||||
name: 'settings Settings',
|
||||
})
|
||||
.click()
|
||||
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||
await page.getByTestId('settings-close-button').click()
|
||||
|
||||
await test.step('swap between small and large files', async () => {
|
||||
await openDebugPanel()
|
||||
// Previously created a file so we need to start back at main.kcl
|
||||
await mainFile.click()
|
||||
await expectCmdLog('[data-message-type="execution-done"]', 60_000)
|
||||
// Click the large file
|
||||
await legoFile.click()
|
||||
// Once it is building, click back to the smaller file
|
||||
await mainFile.click()
|
||||
await expectCmdLog('[data-message-type="execution-done"]', 60_000)
|
||||
await closeDebugPanel()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Renaming in the file tree', () => {
|
||||
test(
|
||||
'A file you have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const checkUnRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const newFileName = 'newFileName'
|
||||
const checkRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
|
||||
const fileToRename = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||
const renamedFile = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'newFileName.kcl' }) })
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
await fileToRename.click()
|
||||
await expect(projectMenuButton).toContainText('fileToRename.kcl')
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('circle(')
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
await test.step('Rename the file', async () => {
|
||||
await fileToRename.click({ button: 'right' })
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFileName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we navigated', async () => {
|
||||
await expect(projectMenuButton).toContainText(newFileName + FILE_EXT)
|
||||
const url = page.url()
|
||||
expect(url).toContain(newFileName)
|
||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
await expect(projectMenuButton).not.toContainText('main.kcl')
|
||||
expect(url).not.toContain('fileToRename.kcl')
|
||||
expect(url).not.toContain('main.kcl')
|
||||
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('circle(')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'A file you do not have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const newFileName = 'newFileName'
|
||||
const checkUnRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const checkRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const fileToRename = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||
const renamedFile = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: newFileName + FILE_EXT }),
|
||||
})
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the file', async () => {
|
||||
await fileToRename.click({ button: 'right' })
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFileName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we have not navigated', async () => {
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(projectMenuButton).not.toContainText(
|
||||
newFileName + FILE_EXT
|
||||
)
|
||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain(newFileName)
|
||||
expect(url).not.toContain('fileToRename.kcl')
|
||||
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('fillet(')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`A folder you're not inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToRename = page.getByRole('button', {
|
||||
name: 'folderToRename',
|
||||
})
|
||||
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const originalFolderName = 'folderToRename'
|
||||
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||
const newFolderName = 'newFolderName'
|
||||
const checkUnRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
const checkRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(folderToRename).toBeVisible()
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFolderName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the folder is renamed, and no navigation occurred', async () => {
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(renamedFolder).toBeVisible()
|
||||
await expect(folderToRename).not.toBeAttached()
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`A folder you are inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToRename = page.getByRole('button', {
|
||||
name: 'folderToRename',
|
||||
})
|
||||
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||
const fileWithinFolder = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||
})
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const originalFolderName = 'folderToRename'
|
||||
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||
const newFolderName = 'newFolderName'
|
||||
const checkUnRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
const checkRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and navigate into folder', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(folderToRename).toBeVisible()
|
||||
await folderToRename.click()
|
||||
await expect(fileWithinFolder).toBeVisible()
|
||||
await fileWithinFolder.click()
|
||||
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
const newUrl = page.url()
|
||||
expect(newUrl).toContain('folderToRename')
|
||||
expect(newUrl).toContain('someFileWithin.kcl')
|
||||
expect(newUrl).not.toContain('main.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await page.waitForTimeout(60000)
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFolderName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the folder is renamed, and navigated to new path', async () => {
|
||||
const urlSnippet = encodeURIComponent(
|
||||
join(newFolderName, 'someFileWithin.kcl')
|
||||
)
|
||||
await page.waitForURL(new RegExp(urlSnippet))
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
await expect(renamedFolder).toBeVisible()
|
||||
await expect(folderToRename).not.toBeAttached()
|
||||
|
||||
// URL is synchronous, so we check the other stuff first
|
||||
const url = page.url()
|
||||
expect(url).not.toContain('main.kcl')
|
||||
expect(url).toContain(newFolderName)
|
||||
expect(url).toContain('someFileWithin.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Deleting items from the file pane', () => {
|
||||
test(
|
||||
`delete file when main.kcl exists, navigate to main.kcl`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const testDir = join(dir, 'testProject')
|
||||
await fsp.mkdir(testDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(testDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(testDir, 'fileToDelete.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText('testProject')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const fileToDelete = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToDelete.kcl' }) })
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and navigate to fileToDelete.kcl', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openFilePanel()
|
||||
|
||||
await fileToDelete.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
await test.step('Delete fileToDelete.kcl', async () => {
|
||||
await fileToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and navigation', async () => {
|
||||
await u.waitForPageLoad()
|
||||
await expect(fileToDelete).not.toBeVisible()
|
||||
await u.closeFilePanel()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('circle(')
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme(
|
||||
'TODO - delete file we have open when main.kcl does not exist',
|
||||
async () => {}
|
||||
)
|
||||
|
||||
test(
|
||||
`Delete folder we are not in, don't navigate`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToDelete'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToDelete', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToDelete = page.getByRole('button', {
|
||||
name: 'folderToDelete',
|
||||
})
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and open project pane', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await u.closeKclCodePanel()
|
||||
await u.openFilePanel()
|
||||
})
|
||||
|
||||
await test.step('Delete folderToDelete', async () => {
|
||||
await folderToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and no navigation', async () => {
|
||||
await expect(folderToDelete).not.toBeAttached()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`Delete folder we are in, navigate to main.kcl`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToDelete'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToDelete', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText('Test Project')
|
||||
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' }),
|
||||
})
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
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')
|
||||
})
|
||||
|
||||
await test.step('Delete folderToDelete', async () => {
|
||||
await folderToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and navigation to main.kcl', async () => {
|
||||
await expect(folderToDelete).not.toBeAttached()
|
||||
await expect(fileWithinFolder).not.toBeAttached()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme('TODO - delete folder we are in, with no main.kcl', async () => {})
|
||||
})
|
270
e2e/playwright/lib/console-error-whitelist.ts
Normal file
@ -0,0 +1,270 @@
|
||||
export const isErrorWhitelisted = (exception: Error) => {
|
||||
// due to the way webkit/Google Chrome report errors, it was necessary
|
||||
// to whitelist similar errors separately for each project
|
||||
let whitelist: {
|
||||
name: string
|
||||
message: string
|
||||
stack: string
|
||||
foundInSpec: string
|
||||
project: 'webkit' | 'Google Chrome'
|
||||
}[] = [
|
||||
{
|
||||
name: '',
|
||||
message: 'undefined',
|
||||
stack: '',
|
||||
foundInSpec: `e2e/playwright/sketch-tests.spec.ts Existing sketch with bad code delete user's code`,
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: '"{"kind"',
|
||||
message:
|
||||
'"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"',
|
||||
stack: '',
|
||||
foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
message: 'false',
|
||||
stack: '',
|
||||
foundInSpec: 'e2e/playwright/testing-segment-overlays.spec.ts',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: '{"kind"',
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
message: 'no connection to send on',
|
||||
stack: '',
|
||||
foundInSpec: 'e2e/playwright/various.spec.ts',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
message: 'sketchGroup not found',
|
||||
stack: '',
|
||||
foundInSpec:
|
||||
'e2e/playwright/testing-selections.spec.ts Deselecting line tool should mean nothing happens on click',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: 'engine error',
|
||||
message:
|
||||
'[{"error_code":"bad_request","message":"Cannot set the camera position with these values"}]',
|
||||
stack: '',
|
||||
foundInSpec:
|
||||
'e2e/playwright/can-create-sketches-on-all-planes-and-their-back-sides.spec.ts XY',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
message: 'no connection to send on',
|
||||
stack: '',
|
||||
foundInSpec:
|
||||
'e2e/playwright/can-create-sketches-on-all-planes-and-their-back-sides.spec.ts XY',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: 'RangeError',
|
||||
message: 'Position 160 is out of range for changeset of length 0',
|
||||
stack: `RangeError: Position 160 is out of range for changeset of length 0
|
||||
at _ChangeSet.mapPos (http://localhost:3000/node_modules/.vite/deps/chunk-3BHLKIA4.js?v=412eae63:756:13)
|
||||
at findSharedChunks (http://localhost:3000/node_modules/.vite/deps/chunk-3BHLKIA4.js?v=412eae63:3045:49)
|
||||
at _RangeSet.compare (http://localhost:3000/node_modules/.vite/deps/chunk-3BHLKIA4.js?v=412eae63:2840:24)
|
||||
at findChangedDeco (http://localhost:3000/node_modules/.vite/deps/chunk-IZYF444B.js?v=412eae63:3320:12)
|
||||
at DocView.update (http://localhost:3000/node_modules/.vite/deps/chunk-IZYF444B.js?v=412eae63:2774:20)
|
||||
at _EditorView.update (http://localhost:3000/node_modules/.vite/deps/chunk-IZYF444B.js?v=412eae63:7056:30)
|
||||
at DOMObserver.flush (http://localhost:3000/node_modules/.vite/deps/chunk-IZYF444B.js?v=412eae63:6621:17)
|
||||
at MutationObserver.<anonymous> (http://localhost:3000/node_modules/.vite/deps/chunk-IZYF444B.js?v=412eae63:6322:14)`,
|
||||
foundInSpec: 'e2e/playwright/editor-tests.spec.ts fold gutters work',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: 'RangeError',
|
||||
message: 'Selection points outside of document',
|
||||
stack: `RangeError: Selection points outside of document
|
||||
+ at checkSelection (http://localhost:3000/node_modules/.vite/deps/chunk-3BHLKIA4.js?v=412eae63:1453:13)
|
||||
+ at new _Transaction (http://localhost:3000/node_modules/.vite/deps/chunk-3BHLKIA4.js?v=412eae63:2014:7)
|
||||
+ at _Transaction.create (http://localhost:3000/node_modules/.vite/deps/chunk-3BHLKIA4.js?v=412eae63:2022:12)
|
||||
+ at resolveTransaction (http://localhost:3000/node_modules/.vite/deps/chunk-3BHLKIA4.js?v=412eae63:2155:24)
|
||||
+ at _EditorState.update (http://localhost:3000/node_modules/.vite/deps/chunk-3BHLKIA4.js?v=412eae63:2281:12)
|
||||
+ at _EditorView.dispatch (http://localhost:3000/node_modules/.vite/deps/chunk-IZYF444B.js?v=412eae63:6988:148)
|
||||
+ at EditorManager.selectRange (http://localhost:3000/src/editor/manager.ts:182:22)
|
||||
+ at AST extrude (http://localhost:3000/src/machines/modelingMachine.ts:828:25)`,
|
||||
foundInSpec: 'e2e/playwright/editor-tests.spec.ts',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message: "TypeError: null is not an object (evaluating 'sg.value')",
|
||||
stack: `Unhandled Promise Rejection: TypeError: null is not an object (evaluating 'sg.value')
|
||||
at unknown (http://localhost:3000/src/clientSideScene/sceneEntities.ts:466:23)
|
||||
at unknown (http://localhost:3000/src/clientSideScene/sceneEntities.ts:454:32)
|
||||
at set up draft line without teardown (http://localhost:3000/src/machines/modelingMachine.ts:983:47)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:1877:24)
|
||||
at handleAction (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:1064:26)
|
||||
at processBlock (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:1087:36)
|
||||
at map ([native code]:0:0)
|
||||
at resolveActions (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:1109:49)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:3639:37)
|
||||
at provide (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:1117:18)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:2452:30)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:1831:43)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:1659:17)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:1643:19)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=0de2e74f:1829:33)
|
||||
at unknown (http://localhost:3000/src/clientSideScene/sceneEntities.ts:263:19)`,
|
||||
foundInSpec: `e2e/playwright/testing-camera-movement.spec.ts Zoom should be consistent when exiting or entering sketches`,
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message: 'false',
|
||||
stack: `Unhandled Promise Rejection: false
|
||||
at unknown (http://localhost:3000/src/clientSideScene/ClientSideSceneComp.tsx:455:78)`,
|
||||
foundInSpec: `e2e/playwright/testing-segment-overlays.spec.ts line-[tagOutsideSketch]`,
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message: `TypeError: null is not an object (evaluating 'programMemory.get(variableDeclarationName).value')`,
|
||||
stack: ` + stack:Unhandled Promise Rejection: TypeError: null is not an object (evaluating 'programMemory.get(variableDeclarationName).value')
|
||||
+ at unknown (http://localhost:3000/src/machines/modelingMachine.ts:911:49)`,
|
||||
foundInSpec: `e2e/playwright/can-create-sketches-on-all-planes-and-their-back-sides.spec.ts`,
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message: `null is not an object (evaluating 'programMemory.get(variableDeclarationName).value')`,
|
||||
stack: `Unhandled Promise Rejection: TypeError: null is not an object (evaluating 'programMemory.get(variableDeclarationName).value')
|
||||
at unknown (http://localhost:3000/src/machines/modelingMachine.ts:911:49)`,
|
||||
foundInSpec: `e2e/playwright/testing-camera-movement.spec.ts Zoom should be consistent when exiting or entering sketches`,
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'TypeError',
|
||||
message: `null is not an object (evaluating 'gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision')`,
|
||||
stack: `TypeError: null is not an object (evaluating 'gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision')
|
||||
at getMaxPrecision (http://localhost:3000/node_modules/.vite/deps/chunk-DEEFU7IG.js?v=d328572b:9557:71)
|
||||
at WebGLCapabilities (http://localhost:3000/node_modules/.vite/deps/chunk-DEEFU7IG.js?v=d328572b:9570:39)
|
||||
at initGLContext (http://localhost:3000/node_modules/.vite/deps/chunk-DEEFU7IG.js?v=d328572b:16993:43)
|
||||
at WebGLRenderer (http://localhost:3000/node_modules/.vite/deps/chunk-DEEFU7IG.js?v=d328572b:17024:18)
|
||||
at SceneInfra (http://localhost:3000/src/clientSideScene/sceneInfra.ts:185:38)
|
||||
at module code (http://localhost:3000/src/lib/singletons.ts:14:41)`,
|
||||
foundInSpec: `e2e/playwright/testing-segment-overlays.spec.ts angledLineToX`,
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
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)\`"}
|
||||
at unknown (http://localhost:3000/src/lang/std/engineConnection.ts:1245:26)`,
|
||||
foundInSpec:
|
||||
'e2e/playwright/onboarding-tests.spec.ts Click through each onboarding step',
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message: 'undefined',
|
||||
stack: '',
|
||||
foundInSpec: `e2e/playwright/sketch-tests.spec.ts Existing sketch with bad code delete user's code`,
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'Fetch API cannot load https',
|
||||
message: '/api.dev.zoo.dev/logout due to access control checks.',
|
||||
stack: `Fetch API cannot load https://api.dev.zoo.dev/logout due to access control checks.
|
||||
at goToSignInPage (http://localhost:3000/src/components/SettingsAuthProvider.tsx:229:15)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:1877:24)
|
||||
at handleAction (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:1064:26)
|
||||
at processBlock (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:1087:36)
|
||||
at map (:1:11)
|
||||
at resolveActions (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:1109:49)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:3639:37)
|
||||
at provide (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:1117:18)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:2452:30)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:1831:43)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:1659:17)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:1643:19)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:1829:33)
|
||||
at unknown (http://localhost:3000/node_modules/.vite/deps/chunk-6FRHHHSJ.js?v=d328572b:2601:23)`,
|
||||
foundInSpec:
|
||||
'e2e/playwright/testing-selections.spec.ts Solids should be select and deletable',
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message: 'ReferenceError: Cannot access uninitialized variable.',
|
||||
stack: `Unhandled Promise Rejection: ReferenceError: Cannot access uninitialized variable.
|
||||
at setDiagnosticsForCurrentErrors (http://localhost:3000/src/lang/KclSingleton.ts:90:18)
|
||||
at kclErrors (http://localhost:3000/src/lang/KclSingleton.ts:82:40)
|
||||
at safeParse (http://localhost:3000/src/lang/KclSingleton.ts:150:9)
|
||||
at unknown (http://localhost:3000/src/lang/KclSingleton.ts:113:32)`,
|
||||
foundInSpec:
|
||||
'e2e/playwright/testing-segment-overlays.spec.ts angledLineToX',
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message: 'sketchGroup not found',
|
||||
stack: `Unhandled Promise Rejection: sketchGroup not found
|
||||
at unknown (http://localhost:3000/src/machines/modelingMachine.ts:911:49)`,
|
||||
foundInSpec:
|
||||
'e2e/playwright/testing-selections.spec.ts Deselecting line tool should mean nothing happens on click',
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'Unhandled Promise Rejection',
|
||||
message:
|
||||
'engine error: [{"error_code":"bad_request","message":"Cannot set the camera position with these values"}]',
|
||||
stack:
|
||||
'Unhandled Promise Rejection: engine error: [{"error_code":"bad_request","message":"Cannot set the camera position with these values"}]',
|
||||
foundInSpec:
|
||||
'e2e/playwright/testing-camera-movement.spec.ts Zoom should be consistent when exiting or entering sketches',
|
||||
project: 'webkit',
|
||||
},
|
||||
{
|
||||
name: 'SecurityError',
|
||||
stack: `SecurityError: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.
|
||||
at <anonymous>:13:5
|
||||
at <anonymous>:18:5
|
||||
at <anonymous>:19:7`,
|
||||
message: `Failed to read the 'localStorage' property from 'Window': Access is denied for this document.`,
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/basic-sketch.spec.ts',
|
||||
},
|
||||
{
|
||||
name: ' - internal_engine',
|
||||
stack: `
|
||||
`,
|
||||
message: `Nothing to export`,
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/regression-tests.spec.ts',
|
||||
},
|
||||
{
|
||||
name: 'SyntaxError',
|
||||
stack: `SyntaxError: Unexpected end of JSON input
|
||||
at crossPlatformFetch (http://localhost:3000/src/lib/crossPlatformFetch.ts:34:31)
|
||||
at async sendTelemetry (http://localhost:3000/src/lib/textToCad.ts:179:3)`,
|
||||
message: `Unexpected end of JSON input`,
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/text-to-cad-tests.spec.ts',
|
||||
},
|
||||
{
|
||||
name: '{"kind"',
|
||||
stack: ``,
|
||||
message: `engine","sourceRanges":[[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',
|
||||
},
|
||||
]
|
||||
|
||||
const cleanString = (str: string) => str.replace(/[`"]/g, '')
|
||||
const foundItem = whitelist.find(
|
||||
(item) =>
|
||||
cleanString(exception.name) === cleanString(item.name) &&
|
||||
cleanString(exception.message).includes(cleanString(item.message))
|
||||
)
|
||||
|
||||
return foundItem !== undefined
|
||||
}
|
97
e2e/playwright/machines.spec.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { setupElectron, tearDown, executorInputPath } from './test-utils'
|
||||
import { join } from 'path'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test(
|
||||
'When machine-api server not found butt is disabled and shows the reason',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
const notFoundText = 'Machine API server was not discovered'
|
||||
await expect(page.getByText(notFoundText).first()).not.toBeVisible()
|
||||
|
||||
// Find the make button
|
||||
const makeButton = page.getByRole('button', { name: 'Make part' })
|
||||
// Make sure the button is visible but disabled
|
||||
await expect(makeButton).toBeVisible()
|
||||
await expect(makeButton).toBeDisabled()
|
||||
|
||||
// When you hover over the button, the tooltip should show
|
||||
// that the machine-api server is not found
|
||||
await makeButton.hover()
|
||||
await expect(page.getByText(notFoundText).first()).toBeVisible()
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'When machine-api server not found home screen & project status shows the reason',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
const notFoundText = 'Machine API server was not discovered'
|
||||
|
||||
await expect(page.getByText(notFoundText)).not.toBeVisible()
|
||||
|
||||
const networkMachineToggle = page.getByTestId('network-machine-toggle')
|
||||
await networkMachineToggle.hover()
|
||||
await expect(page.getByText(notFoundText)).toBeVisible()
|
||||
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(page.getByText(notFoundText).nth(1)).not.toBeVisible()
|
||||
|
||||
await networkMachineToggle.hover()
|
||||
await expect(page.getByText(notFoundText).nth(1)).toBeVisible()
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|