Compare commits

..

2 Commits

Author SHA1 Message Date
55a6155400 WIP: Start using the recursion crate 2024-08-19 11:09:37 -04:00
d41972ee4d Add recursion dependency 2024-08-19 11:09:37 -04:00
380 changed files with 7715 additions and 30159 deletions

View File

@ -1,3 +1,3 @@
[codespell] [codespell]
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,./src-tauri/gen/schemas,.yarn.lock,**/yarn.lock

View File

@ -25,9 +25,7 @@
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure "files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
"rules": { "rules": {
"@typescript-eslint/no-floating-promises": "warn", "@typescript-eslint/no-floating-promises": "warn",
"suggest-no-throw/suggest-no-throw": "off", "testing-library/prefer-screen-queries": "off"
"testing-library/prefer-screen-queries": "off",
"jest/valid-expect": "off"
} }
}, },
{ {

View File

@ -1,4 +1,4 @@
name: build-publish-apps name: build-test-publish-apps
on: on:
pull_request: pull_request:
@ -21,7 +21,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
prepare-files: prepare-json-files:
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows) runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
outputs: outputs:
version: ${{ steps.export_version.outputs.version }} version: ${{ steps.export_version.outputs.version }}
@ -33,19 +33,6 @@ jobs:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' 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${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
- name: Set nightly version - name: Set nightly version
if: github.event_name == 'schedule' if: github.event_name == 'schedule'
run: | run: |
@ -55,50 +42,36 @@ jobs:
# TODO: see if we ned to add updater test URL here https://dl.zoo.dev/releases/modeling-app/updater-test/last_update.json # TODO: see if we ned to add updater test URL here https://dl.zoo.dev/releases/modeling-app/updater-test/last_update.json
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
if: ${{ github.event_name == 'schedule' || env.CUT_RELEASE_PR == 'true' }}
with: with:
name: prepared-files
path: | path: |
package.json package.json
src/wasm-lib/pkg/wasm_lib*
- id: export_version - id: export_version
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT" run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
build-apps: build-test-app-macos:
needs: [prepare-files] needs: [prepare-json-files]
strategy: runs-on: macos-14
fail-fast: false
matrix:
os: [macos-14, windows-2022, ubuntu-22.04]
runs-on: ${{ matrix.os }}
env: env:
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
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: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
name: prepared-files if: github.event_name == 'schedule'
- name: Copy prepared files - name: Copy updated .json files
if: github.event_name == 'schedule'
run: | run: |
ls -R prepared-files ls -l artifact
cp prepared-files/package.json package.json cp artifact/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 - name: Sync node version and setup cache
uses: actions/setup-node@v4 uses: actions/setup-node@v4
@ -108,10 +81,79 @@ jobs:
- run: yarn install - run: yarn install
- run: yarn tronb:vite - name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Run build:wasm
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
# TODO: sign the app (and updater bundle potentially)
- name: Add signing certificate
if: ${{ env.BUILD_RELEASE == 'true' }}
run: chmod +x add-osx-cert.sh && ./add-osx-cert.sh
- name: Build the app for arm64
run: "yarn electron-forge make"
- name: Build the app for x64
run: "yarn electron-forge make --arch x64"
- name: List artifacts
run: "ls -R out/make"
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
- uses: actions/upload-artifact@v3
with:
path: "out/make/*/*/*/*"
build-test-app-windows:
needs: [prepare-json-files]
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
- name: Copy updated .json files
if: github.event_name == 'schedule'
run: |
ls -l artifact
cp artifact/package.json package.json
- 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
- 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: Prepare certificate and variables (Windows only) - name: Prepare certificate and variables (Windows only)
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }} if: ${{ env.BUILD_RELEASE == 'true' }}
run: | run: |
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
cat /d/Certificate_pkcs12.p12 cat /d/Certificate_pkcs12.p12
@ -126,7 +168,7 @@ jobs:
shell: bash shell: bash
- name: Setup certicate with SSM KSP (Windows only) - name: Setup certicate with SSM KSP (Windows only)
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }} if: ${{ env.BUILD_RELEASE == 'true' }}
run: | 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 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 msiexec /i smtools-windows-x64.msi /quiet /qn
@ -136,47 +178,83 @@ jobs:
smksp_cert_sync.exe smksp_cert_sync.exe
shell: cmd shell: cmd
- name: Build the app - name: Build the app for x64
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }} run: "yarn electron-forge make --arch x64"
- name: List artifacts in out/ - name: Build the app for arm64
run: ls -R out run: "yarn electron-forge make --arch arm64"
- name: Prepare the tauri update bundles (macOS) - name: List artifacts
if: ${{ env.BUILD_RELEASE && matrix.os == 'macos-14' }} run: "ls -R out/make"
- name: Sign using Signtool
if: ${{ env.BUILD_RELEASE == 'true' }}
env:
THUMBPRINT: "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D"
X64_FILE: "D:\\a\\modeling-app\\modeling-app\\out\\make\\squirrel.windows\\x64\\Zoo Modeling App-*Setup.exe"
ARM64_FILE: "D:\\a\\modeling-app\\modeling-app\\out\\make\\squirrel.windows\\arm64\\Zoo Modeling App-*Setup.exe"
run: | run: |
for ARCH in arm64 x64; do signtool.exe sign /sha1 ${{ env.THUMBPRINT }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "${{ env.X64_FILE }}"
TAURI_DIR=out/tauri/$VERSION/macos signtool.exe verify /v /pa "${{ env.X64_FILE }}"
TEMP_DIR=temp/$ARCH signtool.exe sign /sha1 ${{ env.THUMBPRINT }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "${{ env.ARM64_FILE }}"
mkdir -p $TAURI_DIR signtool.exe verify /v /pa "${{ env.ARM64_FILE }}"
mkdir -p $TEMP_DIR
unzip out/*-$ARCH-mac.zip -d $TEMP_DIR
tar -czvf "$TAURI_DIR/Zoo Modeling App-$ARCH.app.tar.gz" -C $TEMP_DIR "Zoo Modeling App.app"
yarn tauri signer sign "$TAURI_DIR/Zoo Modeling App-$ARCH.app.tar.gz"
done
ls -R out
- name: Prepare the tauri update bundles (Windows)
if: ${{ env.BUILD_RELEASE && matrix.os == 'windows-2022' }}
run: |
$env:TAURI_DIR="out/tauri/${env:VERSION}/nsis"
mkdir -p ${env:TAURI_DIR}
$env:OUT_FILE="${env:TAURI_DIR}/Zoo Modeling App_${env:VERSION_NO_V}_x64-setup.nsis.zip"
7z a -mm=Copy "${env:OUT_FILE}" ./out/*-x64-win.exe
yarn tauri signer sign "${env:OUT_FILE}"
ls -R out
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: out-${{ matrix.os }} path: "out/make/*/*/*"
path: |
out/Zoo*.* # TODO: Run e2e tests
out/latest*.yml
out/tauri
build-test-app-ubuntu:
needs: [prepare-json-files]
runs-on: ubuntu-22.04
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
- 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
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Run build:wasm
run: "yarn build:wasm${{ env.BUILD_RELEASE == 'true' && '-dev' || ''}}"
- name: Build the app for arm64
run: "yarn electron-forge make --arch arm64"
- name: Build the app for x64
run: "yarn electron-forge make --arch x64"
- name: List artifacts
run: "ls -R out/make"
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back # TODO: add the 'Build for Mac TestFlight (nightly)' stage back
# TODO: add the updater tests back # TODO: sign the app (and updater bundle potentially)
- uses: actions/upload-artifact@v3
with:
path: "out/make/*/*/*"
publish-apps-release: publish-apps-release:
@ -184,169 +262,123 @@ jobs:
permissions: permissions:
contents: write contents: write
if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }} if: ${{ github.event_name == 'release' || github.event_name == 'schedule' }}
needs: [prepare-files, build-apps] needs: [prepare-json-files, build-test-app-macos, build-test-app-windows, build-test-app-ubuntu]
env: env:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} VERSION_NO_V: ${{ needs.prepare-json-files.outputs.version }}
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-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 }} 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) }} NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }}
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }} BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }}
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }} WEBSITE_DIR: ${{ github.event_name == 'release' && 'dl.zoo.dev/releases/modeling-app' || 'dl.zoo.dev/releases/modeling-app/nightly' }}
BUCKET_DIR_TAURI: 'dl.kittycad.io/releases/modeling-app/tauri-compat'
WEBSITE_DIR_TAURI: 'dl.zoo.dev/releases/modeling-app/tauri-compat'
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }} URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
steps: steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with:
name: out-windows-2022
path: out
- uses: actions/download-artifact@v3 - name: Generate the update static endpoint
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: | run: |
RELEASE_DIR=https://${WEBSITE_DIR} ls -l artifact/*/*oo*
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
WINDOWS_X86_64_SIG=`cat artifact/msi/*x64*.msi.zip.sig`
WINDOWS_AARCH64_SIG=`cat artifact/msi/*arm64*.msi.zip.sig`
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
jq --null-input \ jq --null-input \
--arg version "${VERSION}" \ --arg version "${VERSION}" \
--arg pub_date "${PUB_DATE}" \ --arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \ --arg notes "${NOTES}" \
--arg mac_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-mac.dmg" \ --arg darwin_sig "$DARWIN_SIG" \
--arg mac_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-mac.dmg" \ --arg darwin_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}.app.tar.gz" \
--arg windows_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-win.msi" \ --arg windows_x86_64_sig "$WINDOWS_X86_64_SIG" \
--arg windows_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-win.msi" \ --arg windows_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi.zip" \
--arg linux_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-linux.AppImage" \ --arg windows_aarch64_sig "$WINDOWS_AARCH64_SIG" \
--arg linux_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x86_64-linux.AppImage" \ --arg windows_aarch64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_arm64_en-US.msi.zip" \
'{
"version": $version,
"pub_date": $pub_date,
"notes": $notes,
"platforms": {
"dmg-arm64": {
"url": $mac_arm64_url
},
"dmg-x64": {
"url": $mac_x64_url
},
"msi-arm64": {
"url": $windows_arm64_url
},
"msi-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: Generate the update static endpoint for tauri
run: |
TAURI_DIR=out/tauri/$VERSION
MAC_ARM64_SIG=`cat $TAURI_DIR/macos/*-arm64.app.tar.gz.sig`
MAC_X64_SIG=`cat $TAURI_DIR/macos/*-x64.app.tar.gz.sig`
WINDOWS_SIG=`cat $TAURI_DIR/nsis/*.nsis.zip.sig`
RELEASE_DIR=https://${WEBSITE_DIR_TAURI}/${VERSION}
jq --null-input \
--arg version "${VERSION}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg mac_arm64_sig "$MAC_ARM64_SIG" \
--arg mac_arm64_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}-arm64.app.tar.gz" \
--arg mac_x64_sig "$MAC_X64_SIG" \
--arg mac_x64_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}-x64.app.tar.gz" \
--arg windows_sig "$WINDOWS_SIG" \
--arg windows_url "$RELEASE_DIR/nsis/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64-setup.nsis.zip" \
'{ '{
"version": $version, "version": $version,
"pub_date": $pub_date, "pub_date": $pub_date,
"notes": $notes, "notes": $notes,
"platforms": { "platforms": {
"darwin-x86_64": { "darwin-x86_64": {
"signature": $mac_x64_sig, "signature": $darwin_sig,
"url": $mac_x64_url "url": $darwin_url
}, },
"darwin-aarch64": { "darwin-aarch64": {
"signature": $mac_arm64_sig, "signature": $darwin_sig,
"url": $mac_arm64_url "url": $darwin_url
}, },
"windows-x86_64": { "windows-x86_64": {
"signature": $windows_sig, "signature": $windows_x86_64_sig,
"url": $windows_url "url": $windows_x86_64_url
},
"windows-aarch64": {
"signature": $windows_aarch64_sig,
"url": $windows_aarch64_url
} }
} }
}' > last_update.json }' > last_update.json
cat last_update.json cat last_update.json
- name: List artifacts - name: Generate the download static endpoint
run: "ls -R out" 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_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi" \
--arg windows_aarch64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_arm64_en-US.msi" \
'{
"version": $version,
"pub_date": $pub_date,
"notes": $notes,
"platforms": {
"dmg-universal": {
"url": $darwin_url
},
"msi-x86_64": {
"url": $windows_x86_64_url
},
"msi-aarch64": {
"url": $windows_aarch64_url
}
}
}' > last_download.json
cat last_download.json
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.5' uses: 'google-github-actions/auth@v2.1.3'
with: with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}' credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
- name: Set up Google Cloud SDK - name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2.1.0 uses: google-github-actions/setup-gcloud@v2.1.0
with: with:
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }} project_id: kittycadapi
- name: Upload release files to public bucket - name: Upload release files to public bucket
uses: google-github-actions/upload-cloud-storage@v2.2.0 uses: google-github-actions/upload-cloud-storage@v2.1.0
with: with:
path: out path: artifact
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 public bucket for tauri
uses: google-github-actions/upload-cloud-storage@v2.2.0
with:
path: "out/tauri/${{ env.VERSION }}"
glob: '*/Zoo*' glob: '*/Zoo*'
parent: false parent: false
destination: ${{ env.BUCKET_DIR_TAURI }}/${{ env.VERSION }} destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
- name: Upload update endpoint to public bucket for tauri - name: Upload update endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.2.0 uses: google-github-actions/upload-cloud-storage@v2.1.0
with: with:
path: last_update.json path: last_update.json
destination: ${{ env.BUCKET_DIR }} destination: ${{ env.BUCKET_DIR }}
- name: Upload download endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v2.1.0
with:
path: last_download.json
destination: ${{ env.BUCKET_DIR }}
- name: Upload release files to Github - name: Upload release files to Github
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: 'out/Zoo*' files: 'artifact/*/Zoo*'
# TODO: Add GitHub publisher
announce_release: announce_release:
needs: [publish-apps-release] needs: [publish-apps-release]

View File

@ -44,8 +44,6 @@ jobs:
- run: yarn build:wasm - run: yarn build:wasm
- run: yarn xstate:typegen - run: yarn xstate:typegen
- run: yarn tsc - run: yarn tsc
- name: Lint
run: yarn eslint --max-warnings 0 src e2e
check-typos: check-typos:

View File

@ -37,4 +37,4 @@ jobs:
# We specifically want to test the disable-println feature # We specifically want to test the disable-println feature
# Since it is not enabled by default, we need to specify it # Since it is not enabled by default, we need to specify it
# This is used in kcl-lsp # This is used in kcl-lsp
cargo check --all --features disable-println --features pyo3 --features cli cargo check --all --features disable-println --features pyo3

View File

@ -7,7 +7,6 @@ on:
- '**/Cargo.toml' - '**/Cargo.toml'
- '**/Cargo.lock' - '**/Cargo.lock'
- '**/rust-toolchain.toml' - '**/rust-toolchain.toml'
- 'src/wasm-lib/**.kcl'
- .github/workflows/cargo-test.yml - .github/workflows/cargo-test.yml
pull_request: pull_request:
@ -16,7 +15,6 @@ on:
- '**/Cargo.toml' - '**/Cargo.toml'
- '**/Cargo.lock' - '**/Cargo.lock'
- '**/rust-toolchain.toml' - '**/rust-toolchain.toml'
- 'src/wasm-lib/**.kcl'
- .github/workflows/cargo-test.yml - .github/workflows/cargo-test.yml
workflow_dispatch: workflow_dispatch:
permissions: read-all permissions: read-all
@ -38,6 +36,11 @@ jobs:
with: with:
toolchain: stable toolchain: stable
override: true 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 - name: Install vector
run: | run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh

View File

@ -1,31 +0,0 @@
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']
});

View File

@ -139,7 +139,7 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }} if: ${{ !cancelled() && (success() || failure()) }}
with: with:
name: playwright-report-${{ matrix.os }}-snapshot-${{ matrix.shardIndex }}-${{ github.sha }} name: playwright-report-ubuntu-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/ path: playwright-report/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
@ -174,14 +174,14 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: steps.git-check.outputs.modified == 'true' if: steps.git-check.outputs.modified == 'true'
with: with:
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }} name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/ path: playwright-report/
retention-days: 30 retention-days: 30
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }} if: ${{ !cancelled() && (success() || failure()) }}
continue-on-error: true continue-on-error: true
with: with:
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }} name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/ path: test-results/
- name: Run playwright/chrome flow (with retries) - name: Run playwright/chrome flow (with retries)
id: retry id: retry
@ -244,14 +244,14 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }} name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/ path: test-results/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }} name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/ path: playwright-report/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
@ -262,7 +262,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, windows-latest, macos-14] os: [ubuntu-latest, windows-latest, macos-14]
timeout-minutes: 40 timeout-minutes: 30
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: check-rust-changes needs: check-rust-changes
steps: steps:
@ -346,12 +346,12 @@ jobs:
run: yarn build:wasm run: yarn build:wasm
- name: build electron - name: build electron
shell: bash shell: bash
run: yarn tron:package run: yarn electron:package
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }} if: ${{ !cancelled() && (success() || failure()) }}
continue-on-error: true continue-on-error: true
with: with:
name: test-results-${{ matrix.os }}-${{ github.sha }} name: test-results-ubuntu-${{ github.sha }}
path: test-results/ path: test-results/
- name: Run electron tests (with retries) - name: Run electron tests (with retries)
id: retry id: retry
@ -381,7 +381,7 @@ jobs:
echo "retried=true" >>$GITHUB_OUTPUT echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry" echo "run playwright with last failed tests and retry $retry"
if [[ "$IS_UBUNTU" == "true" ]]; then 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 xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn playwright test --config=playwright.electron.config.ts --grep=@electron || true
else else
yarn playwright test --config=playwright.electron.config.ts --grep=@electron || true yarn playwright test --config=playwright.electron.config.ts --grep=@electron || true
fi fi
@ -423,14 +423,14 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }} if: ${{ !cancelled() && (success() || failure()) }}
with: with:
name: test-results-electron-${{ matrix.os }}-${{ github.sha }} name: test-results-electron-${{ github.sha }}
path: test-results/ path: test-results/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }} if: ${{ !cancelled() && (success() || failure()) }}
with: with:
name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }} name: playwright-report-electron-${{ github.sha }}
path: playwright-report/ path: playwright-report/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true

6
.gitignore vendored
View File

@ -54,15 +54,19 @@ e2e/playwright/export-snapshots/*
## generated files ## generated files
src/**/*.typegen.ts src/**/*.typegen.ts
src-tauri/gen
src/wasm-lib/grackle/stdlib_cube_partial.json src/wasm-lib/grackle/stdlib_cube_partial.json
Mac_App_Distribution.provisionprofile Mac_App_Distribution.provisionprofile
*.tsbuildinfo *.tsbuildinfo
src/wasm-lib/pkg
venv venv
.vite/ .vite/
# electron # electron
out/ out/
src-tauri/target
electron-test-projects-dir
electron-test-projects-dir-2

View File

@ -1,344 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.kcl</string>
</array>
<key>CFBundleTypeName</key>
<string>KCL</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.toml</string>
</array>
<key>CFBundleTypeName</key>
<string>TOML</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.gltf</string>
</array>
<key>CFBundleTypeName</key>
<string>glTF</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.glb</string>
</array>
<key>CFBundleTypeName</key>
<string>glb</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.step</string>
</array>
<key>CFBundleTypeName</key>
<string>STEP</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.fbx</string>
</array>
<key>CFBundleTypeName</key>
<string>FBX</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>dev.zoo.sldprt</string>
</array>
<key>CFBundleTypeName</key>
<string>Solidworks Part</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.geometry-definition-format</string>
</array>
<key>CFBundleTypeName</key>
<string>OBJ</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.polygon-file-format</string>
</array>
<key>CFBundleTypeName</key>
<string>PLY</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.standard-tesselated-geometry-format</string>
</array>
<key>CFBundleTypeName</key>
<string>STL</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>public.folder</string>
</array>
<key>CFBundleTypeName</key>
<string>Folders</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.kcl</string>
<key>UTTypeReferenceURL</key>
<string>https://zoo.dev/docs/kcl</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.source-code</string>
<string>public.data</string>
<string>public.text</string>
<string>public.plain-text</string>
<string>public.3d-content</string>
<string>public.script</string>
</array>
<key>UTTypeDescription</key>
<string>KCL (KittyCAD Language) document</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>kcl</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/vnd.zoo.kcl</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.gltf</string>
<key>UTTypeReferenceURL</key>
<string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.text</string>
<string>public.plain-text</string>
<string>public.3d-content</string>
<string>public.json</string>
</array>
<key>UTTypeDescription</key>
<string>Graphics Library Transmission Format (glTF)</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gltf</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/gltf+json</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.glb</string>
<key>UTTypeReferenceURL</key>
<string>https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.3d-content</string>
</array>
<key>UTTypeDescription</key>
<string>Graphics Library Transmission Format (glTF) binary</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>glb</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/gltf-binary</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.step</string>
<key>UTTypeReferenceURL</key>
<string>https://www.loc.gov/preservation/digital/formats/fdd/fdd000448.shtml</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.3d-content</string>
<string>public.text</string>
<string>public.plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>STEP-file, ISO 10303-21</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>step</string>
<string>stp</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/step</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.sldprt</string>
<key>UTTypeReferenceURL</key>
<string>https://docs.fileformat.com/cad/sldprt/</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.3d-content</string>
</array>
<key>UTTypeDescription</key>
<string>Solidworks Part</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>sldprt</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/vnd.solidworks.sldprt</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.fbx</string>
<key>UTTypeReferenceURL</key>
<string>https://en.wikipedia.org/wiki/FBX</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.3d-content</string>
</array>
<key>UTTypeDescription</key>
<string>Autodesk Filmbox (FBX) format</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>fbx</string>
<string>fbxb</string>
</array>
<key>public.mime-type</key>
<array>
<string>model/vnd.autodesk.fbx</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>dev.zoo.toml</string>
<key>UTTypeReferenceURL</key>
<string>https://toml.io/en/</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.text</string>
<string>public.plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>Tom's Obvious Minimal Language</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>kcl</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/toml</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

View File

@ -7,14 +7,6 @@ XSTATE_TYPEGENS := $(wildcard src/machines/*.typegen.ts)
dev: node_modules public/wasm_lib_bg.wasm $(XSTATE_TYPEGENS) dev: node_modules public/wasm_lib_bg.wasm $(XSTATE_TYPEGENS)
yarn start 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) $(XSTATE_TYPEGENS): $(TS_SRC)
yarn xstate typegen 'src/**/*.ts?(x)' yarn xstate typegen 'src/**/*.ts?(x)'

View File

@ -101,7 +101,7 @@ This will start the application and hot-reload on changed.
Devtools can be opened with the usual Cmd/Ctrl-Shift-I. Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
To build, run `yarn tron:package`. To build, run `yarn electron:package`.
## Checking out commits / Bisecting ## Checking out commits / Bisecting
@ -110,6 +110,7 @@ Which commands from setup are one off vs need to be run every time?
The following will need to be run when checking out a new commit and guarantees the build is not stale: The following will need to be run when checking out a new commit and guarantees the build is not stale:
```bash ```bash
yarn install yarn install
yarn wasm-prep
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build 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 yarn start # or yarn build:local && yarn serve for slower but more production-like build
``` ```
@ -188,22 +189,12 @@ For more information on fuzzing you can check out
### Playwright tests ### 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. For a portable way to run Playwright you'll need Docker.
#### Generic example
After that, open a terminal and run: After that, open a terminal and run:
```bash ```bash
docker run --network host --rm --init -it playwright/chrome:playwright-x.xx.x docker run --network host --rm --init -it playwright/chrome:playwright-1.43.1
``` ```
and in another terminal, run: and in another terminal, run:
@ -212,27 +203,21 @@ and in another terminal, run:
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" <test suite> 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`
#### Specific example YOU WILL NEED A PLAYWRIGHT-SECRETS.ENV FILE:
open a terminal and run:
```bash ```bash
docker run --network host --rm --init -it playwright/chrome:playwright-1.46.0 # ./e2e/playwright/playwright-secrets.env
``` token=<your-token>
snapshottoken=<your-snapshot-token>
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('...` 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) (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 run headed
``` ```

24
add-osx-cert.sh Normal file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env sh
# From https://dev.to/rwwagner90/signing-electron-apps-with-github-actions-4cof
KEY_CHAIN=build.keychain
CERTIFICATE_P12=certificate.p12
# Recreate the certificate from the secure environment variable
echo $APPLE_CERTIFICATE | base64 --decode > $CERTIFICATE_P12
#create a keychain
security create-keychain -p actions $KEY_CHAIN
# Make the keychain the default so identities are found
security default-keychain -s $KEY_CHAIN
# Unlock the keychain
security unlock-keychain -p actions $KEY_CHAIN
security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $APPLE_CERTIFICATE_PASSWORD -T /usr/bin/codesign;
security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN
# remove certs
rm -fr *.p12

View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -32,21 +32,17 @@ layout: manual
* [`chamfer`](kcl/chamfer) * [`chamfer`](kcl/chamfer)
* [`circle`](kcl/circle) * [`circle`](kcl/circle)
* [`close`](kcl/close) * [`close`](kcl/close)
* [`cm`](kcl/cm)
* [`cos`](kcl/cos) * [`cos`](kcl/cos)
* [`e`](kcl/e) * [`e`](kcl/e)
* [`extrude`](kcl/extrude) * [`extrude`](kcl/extrude)
* [`fillet`](kcl/fillet) * [`fillet`](kcl/fillet)
* [`floor`](kcl/floor) * [`floor`](kcl/floor)
* [`ft`](kcl/ft)
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge) * [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
* [`getOppositeEdge`](kcl/getOppositeEdge) * [`getOppositeEdge`](kcl/getOppositeEdge)
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge) * [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
* [`helix`](kcl/helix) * [`helix`](kcl/helix)
* [`hole`](kcl/hole) * [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`import`](kcl/import) * [`import`](kcl/import)
* [`inch`](kcl/inch)
* [`int`](kcl/int) * [`int`](kcl/int)
* [`lastSegX`](kcl/lastSegX) * [`lastSegX`](kcl/lastSegX)
* [`lastSegY`](kcl/lastSegY) * [`lastSegY`](kcl/lastSegY)
@ -56,15 +52,11 @@ layout: manual
* [`line`](kcl/line) * [`line`](kcl/line)
* [`lineTo`](kcl/lineTo) * [`lineTo`](kcl/lineTo)
* [`ln`](kcl/ln) * [`ln`](kcl/ln)
* [`loft`](kcl/loft)
* [`log`](kcl/log) * [`log`](kcl/log)
* [`log10`](kcl/log10) * [`log10`](kcl/log10)
* [`log2`](kcl/log2) * [`log2`](kcl/log2)
* [`m`](kcl/m)
* [`max`](kcl/max) * [`max`](kcl/max)
* [`min`](kcl/min) * [`min`](kcl/min)
* [`mm`](kcl/mm)
* [`offsetPlane`](kcl/offsetPlane)
* [`patternCircular2d`](kcl/patternCircular2d) * [`patternCircular2d`](kcl/patternCircular2d)
* [`patternCircular3d`](kcl/patternCircular3d) * [`patternCircular3d`](kcl/patternCircular3d)
* [`patternLinear2d`](kcl/patternLinear2d) * [`patternLinear2d`](kcl/patternLinear2d)
@ -90,7 +82,6 @@ layout: manual
* [`tan`](kcl/tan) * [`tan`](kcl/tan)
* [`tangentialArc`](kcl/tangentialArc) * [`tangentialArc`](kcl/tangentialArc)
* [`tangentialArcTo`](kcl/tangentialArcTo) * [`tangentialArcTo`](kcl/tangentialArcTo)
* [`tangentialArcToRelative`](kcl/tangentialArcToRelative)
* [`tau`](kcl/tau) * [`tau`](kcl/tau)
* [`toDegrees`](kcl/toDegrees) * [`toDegrees`](kcl/toDegrees)
* [`toRadians`](kcl/toRadians) * [`toRadians`](kcl/toRadians)
@ -98,4 +89,3 @@ layout: manual
* [`xLineTo`](kcl/xLineTo) * [`xLineTo`](kcl/xLineTo)
* [`yLine`](kcl/yLine) * [`yLine`](kcl/yLine)
* [`yLineTo`](kcl/yLineTo) * [`yLineTo`](kcl/yLineTo)
* [`yd`](kcl/yd)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,66 +0,0 @@
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()
}
)
})

View File

@ -96,49 +96,33 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
} }
// deselect line tool // deselect line tool
const btnLine = page.getByTestId('line') await page.getByTestId('line').click()
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) const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect await expect
.poll(async () => u.getGreatestPixDiff(line1, TEST_COLORS.WHITE)) .poll(async () => u.getGreatestPixDiff(line1, TEST_COLORS.WHITE))
.toBeLessThan(3) .toBeLessThan(3)
await page.waitForTimeout(100)
await expect await expect
.poll(async () => u.getGreatestPixDiff(line1, [249, 249, 249])) .poll(() => u.getGreatestPixDiff(line1, [249, 249, 249]))
.toBeLessThan(3) .toBeLessThan(3)
await page.waitForTimeout(100)
} }
// click between first two clicks to get center of the line // click between first two clicks to get center of the line
await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10)
await page.waitForTimeout(100) await page.waitForTimeout(100)
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect( expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)
).toBeLessThan(3)
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3) await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
} }
// hold down shift // hold down shift
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
await page.waitForTimeout(100)
// click between the latest two clicks to get center of the line // click between the latest two clicks to get center of the line
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 20)
await page.waitForTimeout(100)
// selected two lines therefore there should be two cursors // selected two lines therefore there should be two cursors
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect(page.locator('.cm-cursor')).toHaveCount(2) await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.waitForTimeout(100)
} }
await page.getByRole('button', { name: 'Length: open menu' }).click() await page.getByRole('button', { name: 'Length: open menu' }).click()

View File

@ -1,16 +1,8 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import { import { getUtils, setup, tearDown } from './test-utils'
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { join } from 'path'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates' import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
import fsp from 'fs/promises'
test.beforeEach(async ({ context, page }) => { test.beforeEach(async ({ context, page }) => {
await setup(context, page) await setup(context, page)
@ -27,19 +19,9 @@ test.describe('Code pane and errors', () => {
const u = await getUtils(page) const u = await getUtils(page)
// Load the app with the working starter code // Load the app with the working starter code
await page.addInitScript(() => { await page.addInitScript((code) => {
localStorage.setItem( localStorage.setItem('persistCode', code)
'persistCode', }, bracket)
`// 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 page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
@ -101,7 +83,7 @@ const extrude001 = extrude(5, sketch001)`
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
await expect(page.getByText('Unexpected token: |').first()).toBeVisible() await expect(page.getByText('Unexpected token').first()).toBeVisible()
// Close the code pane // Close the code pane
await codePaneButton.click() await codePaneButton.click()
@ -124,7 +106,7 @@ const extrude001 = extrude(5, sketch001)`
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
await expect(page.getByText('Unexpected token: |').first()).toBeVisible() await expect(page.getByText('Unexpected token').first()).toBeVisible()
}) })
test('When error is not in view you can click the badge to scroll to it', async ({ test('When error is not in view you can click the badge to scroll to it', async ({
@ -235,79 +217,3 @@ const extrude001 = extrude(5, sketch001)`
).toBeVisible() ).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()
}
)

View File

@ -12,47 +12,50 @@ test.afterEach(async ({ page }, testInfo) => {
}) })
test.describe('Command bar tests', () => { test.describe('Command bar tests', () => {
test('Extrude from command bar selects extrude line after', async ({ // TODO fixme: enter is not working in the command bar
page, test.fixme(
}) => { 'Extrude from command bar selects extrude line after',
await page.addInitScript(async () => { async ({ page }) => {
localStorage.setItem( await page.addInitScript(async () => {
'persistCode', localStorage.setItem(
`const sketch001 = startSketchOn('XY') 'persistCode',
`const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %) |> startProfileAt([-10, -10], %)
|> line([20, 0], %) |> line([20, 0], %)
|> line([0, 20], %) |> line([0, 20], %)
|> xLine(-20, %) |> xLine(-20, %)
|> close(%) |> close(%)
` `
)
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// Click the line of code for xLine.
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Extrude' }).click()
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
await page.waitForTimeout(200)
await expect(page.locator('.cm-activeLine')).toHaveText(
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
) )
}) }
)
const u = await getUtils(page) // TODO fixme: enter is not working in the command bar
await page.setViewportSize({ width: 1200, height: 500 }) test.fixme('Fillet from command bar', async ({ page }) => {
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// Click the line of code for xLine.
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Extrude' }).click()
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
await page.waitForTimeout(200)
await expect(page.locator('.cm-activeLine')).toHaveText(
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
)
})
test('Fillet from command bar', async ({ page }) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -124,7 +127,7 @@ const extrude001 = extrude(-10, sketch001)`
await expect(cmdSearchBar).not.toBeVisible() await expect(cmdSearchBar).not.toBeVisible()
// Now try the same, but with the keyboard shortcut, check focus // Now try the same, but with the keyboard shortcut, check focus
await page.keyboard.press('ControlOrMeta+K') await page.keyboard.press('Meta+K')
await expect(cmdSearchBar).toBeVisible() await expect(cmdSearchBar).toBeVisible()
await expect(cmdSearchBar).toBeFocused() await expect(cmdSearchBar).toBeFocused()
@ -185,7 +188,7 @@ const extrude001 = extrude(-10, sketch001)`
await page.locator('.cm-content').click() await page.locator('.cm-content').click()
// Now try the same, but with the keyboard shortcut, check focus // Now try the same, but with the keyboard shortcut, check focus
await page.keyboard.press('ControlOrMeta+K') await page.keyboard.press('Meta+K')
let cmdSearchBar = page.getByPlaceholder('Search commands') let cmdSearchBar = page.getByPlaceholder('Search commands')
await expect(cmdSearchBar).toBeVisible() await expect(cmdSearchBar).toBeVisible()
@ -250,7 +253,7 @@ const extrude001 = extrude(-10, sketch001)`
await page.getByRole('button', { name: 'Extrude' }).isEnabled() await page.getByRole('button', { name: 'Extrude' }).isEnabled()
let cmdSearchBar = page.getByPlaceholder('Search commands') let cmdSearchBar = page.getByPlaceholder('Search commands')
await page.keyboard.press('ControlOrMeta+K') await page.keyboard.press('Meta+K')
await expect(cmdSearchBar).toBeVisible() await expect(cmdSearchBar).toBeVisible()
// Search for extrude command and choose it // Search for extrude command and choose it

View File

@ -9,7 +9,6 @@ test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo) await tearDown(page, testInfo)
}) })
test.describe('Copilot ghost text', () => { test.describe('Copilot ghost text', () => {
// eslint-disable-next-line jest/valid-title
test.skip(true, 'Needs to get covered again') test.skip(true, 'Needs to get covered again')
test('completes code in empty file', async ({ page }) => { test('completes code in empty file', async ({ page }) => {
@ -332,6 +331,7 @@ test.describe('Copilot ghost text', () => {
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
await u.codeLocator.click() await u.codeLocator.click()
await expect(page.locator('.cm-content')).toHaveText(``) 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. // Going elsewhere in the code should hide the ghost text.
await page.keyboard.down('ControlOrMeta') await page.keyboard.down(CtrlKey)
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
await page.keyboard.press('KeyZ') await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up(CtrlKey)
await page.keyboard.up('Shift') await page.keyboard.up('Shift')
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
@ -367,6 +367,8 @@ test.describe('Copilot ghost text', () => {
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
await page.waitForTimeout(800) await page.waitForTimeout(800)
await u.codeLocator.click() await u.codeLocator.click()
await expect(page.locator('.cm-content')).toHaveText(``) await expect(page.locator('.cm-content')).toHaveText(``)
@ -379,17 +381,17 @@ test.describe('Copilot ghost text', () => {
await page.waitForTimeout(800) await page.waitForTimeout(800)
// Ctrl+z // Ctrl+z
await page.keyboard.down('ControlOrMeta') await page.keyboard.down(CtrlKey)
await page.keyboard.press('KeyZ') await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up(CtrlKey)
await expect(page.locator('.cm-content')).toHaveText(``) await expect(page.locator('.cm-content')).toHaveText(``)
// Ctrl+shift+z // Ctrl+shift+z
await page.keyboard.down('ControlOrMeta') await page.keyboard.down(CtrlKey)
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
await page.keyboard.press('KeyZ') await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up(CtrlKey)
await page.keyboard.up('Shift') await page.keyboard.up('Shift')
await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`) await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`)
@ -408,14 +410,14 @@ test.describe('Copilot ghost text', () => {
) )
// Once for the enter. // Once for the enter.
await page.keyboard.down('ControlOrMeta') await page.keyboard.down(CtrlKey)
await page.keyboard.press('KeyZ') await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up(CtrlKey)
// Once for the text. // Once for the text.
await page.keyboard.down('ControlOrMeta') await page.keyboard.down(CtrlKey)
await page.keyboard.press('KeyZ') await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up(CtrlKey)
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()

View File

@ -1,196 +0,0 @@
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'
)
const modelStateIndicatorLoading = page.getByTestId(
'model-state-indicator-loading'
)
await expect(modelStateIndicatorLoading).toBeVisible()
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(477481)
// 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()
}
)

View File

@ -16,6 +16,7 @@ test.describe('Editor tests', () => {
await page.setViewportSize({ width: 1000, height: 500 }) await page.setViewportSize({ width: 1000, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
// check no error to begin with // check no error to begin with
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
@ -28,9 +29,9 @@ test.describe('Editor tests', () => {
|> line([-20, 0], %) |> line([-20, 0], %)
|> close(%)`) |> close(%)`)
await page.keyboard.down('ControlOrMeta') await page.keyboard.down(CtrlKey)
await page.keyboard.press('/') await page.keyboard.press('/')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up(CtrlKey)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XY') .toHaveText(`const sketch001 = startSketchOn('XY')
@ -41,9 +42,9 @@ test.describe('Editor tests', () => {
// |> close(%)`) // |> close(%)`)
// uncomment the code // uncomment the code
await page.keyboard.down('ControlOrMeta') await page.keyboard.down(CtrlKey)
await page.keyboard.press('/') await page.keyboard.press('/')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up(CtrlKey)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XY') .toHaveText(`const sketch001 = startSketchOn('XY')
@ -84,63 +85,6 @@ test.describe('Editor tests', () => {
|> close(%)`) |> 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 }) => { test('fold gutters work', async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -204,7 +148,9 @@ test.describe('Editor tests', () => {
// Delete all the code. // Delete all the code.
await page.locator('.cm-content').click() await page.locator('.cm-content').click()
// Select all // Select all
await page.keyboard.press('ControlOrMeta+A') await page.keyboard.press('Control+A')
await page.keyboard.press('Backspace')
await page.keyboard.press('Meta+A')
await page.keyboard.press('Backspace') await page.keyboard.press('Backspace')
await expect(page.locator('.cm-content')).toHaveText(``) await expect(page.locator('.cm-content')).toHaveText(``)
@ -298,67 +244,6 @@ test.describe('Editor tests', () => {
|> close(%)`) |> 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 }) => { test('if you write kcl with lint errors you get lints', async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1000, height: 500 }) await page.setViewportSize({ width: 1000, height: 500 })
@ -469,7 +354,7 @@ test.describe('Editor tests', () => {
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') 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 // select the line that's causing the error and delete it
await page.getByText('$ error').click() await page.getByText('$ error').click()
@ -517,7 +402,7 @@ test.describe('Editor tests', () => {
const width = 0.500 const width = 0.500
const height = 0.500 const height = 0.500
const dia = 4 const dia = 4
fn squareHole = (l, w) => { fn squareHole = (l, w) => {
const squareHoleSketch = startSketchOn('XY') const squareHoleSketch = startSketchOn('XY')
|> startProfileAt([-width / 2, -length / 2], %) |> startProfileAt([-width / 2, -length / 2], %)
@ -829,15 +714,17 @@ test.describe('Editor tests', () => {
|> close(%)`) |> close(%)`)
}) })
test('Can undo a sketch modification with ctrl+z', async ({ page }) => { // failing for the same reason as "Can edit a sketch that has been extruded in the same pipe"
// please fix together
test.fixme('Can undo a sketch modification with ctrl+z', async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const sketch001 = startSketchOn('XZ') `const sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -10.01], %) |> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %) |> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -5.38], %)
|> close(%) |> close(%)
|> extrude(5, %)` |> extrude(5, %)`
) )
@ -872,11 +759,11 @@ test.describe('Editor tests', () => {
}) })
await page.waitForTimeout(100) await page.waitForTimeout(100)
const startPX = [665, 397] const startPX = [665, 458]
const dragPX = 40 const dragPX = 40
await page.getByText('startProfileAt([4.61, -10.01], %)').click() await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect( await expect(
page.getByRole('button', { name: 'Edit Sketch' }) page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible() ).toBeVisible()
@ -914,7 +801,7 @@ test.describe('Editor tests', () => {
// drag tangentialArcTo handle // drag tangentialArcTo handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]') const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.dragAndDrop('#stream', '#stream', { await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 }, sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 },
targetPosition: { targetPosition: {
x: tangentEnd.x + dragPX, x: tangentEnd.x + dragPX,
y: tangentEnd.y + dragPX, y: tangentEnd.y + dragPX,
@ -926,12 +813,12 @@ test.describe('Editor tests', () => {
// expect the code to have changed // expect the code to have changed
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.12, -12.68], %) |> startProfileAt([7.12, -16.82], %)
|> line([15.39, -2.78], %) |> line([15.4, -2.74], %)
|> tangentialArcTo([27.6, -3.05], %) |> tangentialArcTo([24.95, -5.38], %)
|> close(%) |> line([2.65, -2.69], %)
|> extrude(5, %) |> close(%)
`) |> extrude(5, %)`)
// Hit undo // Hit undo
await page.keyboard.down('Control') await page.keyboard.down('Control')
@ -940,11 +827,11 @@ test.describe('Editor tests', () => {
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.12, -12.68], %) |> startProfileAt([7.12, -16.82], %)
|> line([15.39, -2.78], %) |> line([15.4, -2.74], %)
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -5.38], %)
|> close(%) |> close(%)
|> extrude(5, %)`) |> extrude(5, %)`)
// Hit undo again. // Hit undo again.
await page.keyboard.down('Control') await page.keyboard.down('Control')
@ -953,12 +840,11 @@ test.describe('Editor tests', () => {
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.12, -12.68], %) |> startProfileAt([7.12, -16.82], %)
|> line([12.73, -0.09], %) |> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -5.38], %)
|> close(%) |> close(%)
|> extrude(5, %) |> extrude(5, %)`)
`)
// Hit undo again. // Hit undo again.
await page.keyboard.down('Control') await page.keyboard.down('Control')
@ -968,9 +854,9 @@ test.describe('Editor tests', () => {
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -10.01], %) |> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %) |> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -5.38], %)
|> close(%) |> close(%)
|> extrude(5, %)`) |> extrude(5, %)`)
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -1,204 +0,0 @@
import { test, expect } from '@playwright/test'
import * as fsp from 'fs/promises'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
test.beforeEach(async ({ context, page }) => {
await setup(context, page)
})
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,
folderSetupFn: async () => {},
})
const {
panesOpen,
createAndSelectProject,
pasteCodeInEditor,
createNewFileAndSelect,
renameFile,
selectFile,
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'
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 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()
}
)
})

View File

@ -1,97 +0,0 @@
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' })
// 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()
}
)

View File

@ -1,13 +1,6 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import { join } from 'path'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import { import { getUtils, setup, setupElectron, tearDown } from './test-utils'
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { import {
@ -357,11 +350,10 @@ test(
const { electronApp, page } = await setupElectron({ const { electronApp, page } = await setupElectron({
testInfo, testInfo,
folderSetupFn: async (dir) => { folderSetupFn: async (dir) => {
const routerTemplateDir = join(dir, 'router-template-slate') await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
await fsp.mkdir(routerTemplateDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('router-template-slate.kcl'), 'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
join(routerTemplateDir, 'main.kcl') `${dir}/router-template-slate/main.kcl`
) )
}, },
}) })

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,6 @@
import { test, expect, Page } from '@playwright/test' import { test, expect, Page } from '@playwright/test'
import { join } from 'path'
import * as fsp from 'fs/promises' import * as fsp from 'fs/promises'
import { import { getUtils, setup, setupElectron, tearDown } from './test-utils'
getUtils,
setup,
setupElectron,
tearDown,
executorInputPath,
} from './test-utils'
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates' import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
@ -201,7 +194,7 @@ const sketch001 = startSketchAt([-0, -0])
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
await expect(page.getByText('Unexpected token: |').first()).toBeVisible() await expect(page.getByText('Unexpected token').first()).toBeVisible()
// Okay execution finished, let's start editing text below the error. // Okay execution finished, let's start editing text below the error.
await u.codeLocator.click() await u.codeLocator.click()
@ -243,13 +236,9 @@ const sketch001 = startSketchAt([-0, -0])
page, page,
}) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript( await page.addInitScript(async (code) => {
async ({ code }) => { localStorage.setItem('persistCode', code)
localStorage.setItem('persistCode', code) }, TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR)
;(window as any).playwrightSkipFilePicker = true
},
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
)
await page.setViewportSize({ width: 1000, height: 500 }) await page.setViewportSize({ width: 1000, height: 500 })
@ -336,7 +325,7 @@ const sketch001 = startSketchAt([-0, -0])
await expect(exportingToastMessage).toBeVisible() await expect(exportingToastMessage).toBeVisible()
// Expect it to succeed. // Expect it to succeed.
await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }) await expect(exportingToastMessage).not.toBeVisible()
await expect(errorToastMessage).not.toBeVisible() await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible() await expect(engineErrorToastMessage).not.toBeVisible()
@ -348,7 +337,6 @@ const sketch001 = startSketchAt([-0, -0])
}) => { }) => {
// This is being weird on ubuntu and windows. // This is being weird on ubuntu and windows.
test.skip( test.skip(
// eslint-disable-next-line jest/valid-title
process.platform === 'linux' || process.platform === 'win32', process.platform === 'linux' || process.platform === 'win32',
'This test is being weird on ubuntu' 'This test is being weird on ubuntu'
) )
@ -358,7 +346,6 @@ const sketch001 = startSketchAt([-0, -0])
await page.addInitScript( await page.addInitScript(
async ({ code }) => { async ({ code }) => {
localStorage.setItem('persistCode', code) localStorage.setItem('persistCode', code)
;(window as any).playwrightSkipFilePicker = true
}, },
{ {
code: bracket, code: bracket,
@ -394,22 +381,20 @@ const sketch001 = startSketchAt([-0, -0])
await test.step('The second export is blocked', async () => { await test.step('The second export is blocked', async () => {
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
await Promise.all([ await expect(exportingToastMessage).toBeVisible()
expect(exportingToastMessage.first()).toBeVisible(), await expect(alreadyExportingToastMessage).toBeVisible()
expect(alreadyExportingToastMessage).toBeVisible(),
]) await page.waitForTimeout(1000)
}) })
await test.step('The first export still succeeds', async () => { await test.step('The first export still succeeds', async () => {
await Promise.all([ await expect(exportingToastMessage).not.toBeVisible()
expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }), await expect(errorToastMessage).not.toBeVisible()
expect(errorToastMessage).not.toBeVisible(), await expect(engineErrorToastMessage).not.toBeVisible()
expect(engineErrorToastMessage).not.toBeVisible(),
expect(successToastMessage).toBeVisible({ timeout: 15_000 }), await expect(successToastMessage).toBeVisible()
expect(alreadyExportingToastMessage).not.toBeVisible({
timeout: 15_000, await expect(alreadyExportingToastMessage).not.toBeVisible()
}),
])
}) })
}) })
@ -422,12 +407,10 @@ const sketch001 = startSketchAt([-0, -0])
await expect(exportingToastMessage).toBeVisible() await expect(exportingToastMessage).toBeVisible()
// Expect it to succeed. // Expect it to succeed.
await Promise.all([ await expect(exportingToastMessage).not.toBeVisible()
expect(exportingToastMessage).not.toBeVisible(), await expect(errorToastMessage).not.toBeVisible()
expect(errorToastMessage).not.toBeVisible(), await expect(engineErrorToastMessage).not.toBeVisible()
expect(engineErrorToastMessage).not.toBeVisible(), await expect(alreadyExportingToastMessage).not.toBeVisible()
expect(alreadyExportingToastMessage).not.toBeVisible(),
])
await expect(successToastMessage).toBeVisible() await expect(successToastMessage).toBeVisible()
}) })
@ -440,11 +423,10 @@ const sketch001 = startSketchAt([-0, -0])
const { electronApp, page } = await setupElectron({ const { electronApp, page } = await setupElectron({
testInfo, testInfo,
folderSetupFn: async (dir) => { folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket') await fsp.mkdir(`${dir}/bracket`, { recursive: true })
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), 'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
join(bracketDir, 'main.kcl') `${dir}/bracket/main.kcl`
) )
}, },
}) })
@ -490,7 +472,7 @@ async function clickExportButton(page: Page) {
// Click the export button // Click the export button
await exportButton.click() await exportButton.click()
// Click the gltf. // Click the stl.
const gltfOption = page.getByRole('option', { name: 'glTF' }) const gltfOption = page.getByRole('option', { name: 'glTF' })
await expect(gltfOption).toBeVisible() await expect(gltfOption).toBeVisible()

View File

@ -344,108 +344,111 @@ test.describe('Sketch tests', () => {
}) })
}) })
test('Can edit a sketch that has been extruded in the same pipe', async ({ // failing for the same reason as "Can undo a sketch modification with ctrl+z"
page, // please fix together
}) => { test.fixme(
const u = await getUtils(page) 'Can edit a sketch that has been extruded in the same pipe',
await page.addInitScript(async () => { async ({ page }) => {
localStorage.setItem( const u = await getUtils(page)
'persistCode', await page.addInitScript(async () => {
`const sketch001 = startSketchOn('XZ') localStorage.setItem(
|> startProfileAt([4.61, -10.01], %) 'persistCode',
`const sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %) |> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -0.38], %) |> tangentialArcTo([24.95, -5.38], %)
|> close(%) |> close(%)
|> extrude(5, %)` |> extrude(5, %)`
) )
}) })
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await u.sendCustomCmd({ await u.sendCustomCmd({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: uuidv4(), cmd_id: uuidv4(),
cmd: { cmd: {
type: 'default_camera_look_at', type: 'default_camera_look_at',
vantage: { x: 0, y: -1250, z: 580 }, vantage: { x: 0, y: -1250, z: 580 },
center: { x: 0, y: 0, z: 0 }, center: { x: 0, y: 0, z: 0 },
up: { x: 0, y: 0, z: 1 }, up: { x: 0, y: 0, z: 1 },
}, },
}) })
await page.waitForTimeout(100) await page.waitForTimeout(100)
await u.sendCustomCmd({ await u.sendCustomCmd({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: uuidv4(), cmd_id: uuidv4(),
cmd: { cmd: {
type: 'default_camera_get_settings', type: 'default_camera_get_settings',
}, },
}) })
await page.waitForTimeout(100) await page.waitForTimeout(100)
const startPX = [665, 397] const startPX = [665, 458]
const dragPX = 40 const dragPX = 40
await page.getByText('startProfileAt([4.61, -10.01], %)').click() await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect( await expect(
page.getByRole('button', { name: 'Edit Sketch' }) page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible() ).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400) await page.waitForTimeout(400)
let prevContent = await page.locator('.cm-content').innerText() let prevContent = await page.locator('.cm-content').innerText()
await expect(page.getByTestId('segment-overlay')).toHaveCount(2) await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
// drag startProfieAt handle // drag startProfieAt handle
await page.dragAndDrop('#stream', '#stream', { await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: startPX[0], y: startPX[1] }, sourcePosition: { x: startPX[0], y: startPX[1] },
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX }, targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
}) })
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent) await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText() prevContent = await page.locator('.cm-content').innerText()
// drag line handle // drag line handle
await page.waitForTimeout(100) await page.waitForTimeout(100)
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]') const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.dragAndDrop('#stream', '#stream', { await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y }, sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX }, targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
}) })
await expect(page.locator('.cm-content')).not.toHaveText(prevContent) await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText() prevContent = await page.locator('.cm-content').innerText()
// drag tangentialArcTo handle // drag tangentialArcTo handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]') const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.dragAndDrop('#stream', '#stream', { await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 }, sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 },
targetPosition: { targetPosition: {
x: tangentEnd.x + dragPX, x: tangentEnd.x + dragPX,
y: tangentEnd.y + dragPX, y: tangentEnd.y + dragPX,
}, },
}) })
await page.waitForTimeout(100) await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent) await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed // expect the code to have changed
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([7.12, -12.68], %) |> startProfileAt([7.12, -16.82], %)
|> line([15.39, -2.78], %) |> line([15.4, -2.74], %)
|> tangentialArcTo([27.6, -3.05], %) |> tangentialArcTo([24.95, -5.38], %)
|> close(%) |> line([2.65, -2.69], %)
|> extrude(5, %) |> close(%)
`) |> extrude(5, %)`)
}) }
)
test('Can edit a sketch that has been revolved in the same pipe', async ({ test('Can edit a sketch that has been revolved in the same pipe', async ({
page, page,
@ -599,7 +602,7 @@ test.describe('Sketch tests', () => {
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
// exit the sketch, reset relative clicker // exit the sketch, reset relative clicker
await click00r(undefined, undefined) click00r(undefined, undefined)
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click() await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')

View File

@ -53,7 +53,6 @@ test(
async ({ page, context }) => { async ({ page, context }) => {
// skip on macos and windows. // skip on macos and windows.
test.skip( test.skip(
// eslint-disable-next-line jest/valid-title
process.platform === 'darwin' || process.platform === 'win32', process.platform === 'darwin' || process.platform === 'win32',
'Skip on macos and windows' 'Skip on macos and windows'
) )
@ -964,69 +963,3 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
}) })
}) })
}) })
test('theme persists', async ({ page, context }) => {
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)
|> extrude(10, %)
`
)
}, KCL_DEFAULT_LENGTH)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await page.waitForTimeout(500)
// await page.getByRole('link', { name: 'Settings Settings (tooltip)' }).click()
await expect(page.getByTestId('settings-link')).toBeVisible()
await page.getByTestId('settings-link').click()
// open user settingns
await page.getByRole('radio', { name: 'person User' }).click()
await page.getByTestId('app-theme').selectOption('light')
await page.getByTestId('settings-close-button').click()
const networkToggle = page.getByTestId('network-toggle')
// simulate network down
await u.emulateNetworkConditions({
offline: true,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
// Disconnect and reconnect to check the theme persists through a reload
// Expect the network to be down
await expect(networkToggle).toContainText('Offline')
// simulate network up
await u.emulateNetworkConditions({
offline: false,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
})
await expect(networkToggle).toContainText('Connected')
await expect(page.getByText('building scene')).not.toBeVisible()
await expect(page, 'expect screenshot to have light theme').toHaveScreenshot({
maxDiffPixels: 100,
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Some files were not shown because too many files have changed in this diff Show More