Merge remote-tracking branch 'origin' into ryanrosello-og/playwright-test-coverage
@ -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,./src-tauri/gen/schemas,.yarn.lock,**/yarn.lock
|
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock
|
||||||
|
|||||||
@ -2,7 +2,9 @@ NODE_ENV=development
|
|||||||
DEV=true
|
DEV=true
|
||||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
||||||
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
||||||
|
BASE_URL=https://api.dev.zoo.dev
|
||||||
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
|
||||||
|
#VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
||||||
|
|||||||
@ -13,6 +13,8 @@
|
|||||||
"plugin:css-modules/recommended"
|
"plugin:css-modules/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
|
"@typescript-eslint/no-misused-promises": "error",
|
||||||
"semi": [
|
"semi": [
|
||||||
"error",
|
"error",
|
||||||
"never"
|
"never"
|
||||||
@ -24,7 +26,6 @@
|
|||||||
{
|
{
|
||||||
"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",
|
|
||||||
"suggest-no-throw/suggest-no-throw": "off",
|
"suggest-no-throw/suggest-no-throw": "off",
|
||||||
"testing-library/prefer-screen-queries": "off",
|
"testing-library/prefer-screen-queries": "off",
|
||||||
"jest/valid-expect": "off"
|
"jest/valid-expect": "off"
|
||||||
|
|||||||
333
.github/workflows/build-test-publish-apps.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: build-test-publish-apps
|
name: build-publish-apps
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
@ -21,7 +21,7 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prepare-json-files:
|
prepare-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,6 +33,19 @@ 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"
|
||||||
|
|
||||||
- name: Set nightly version
|
- name: Set nightly version
|
||||||
if: github.event_name == 'schedule'
|
if: github.event_name == 'schedule'
|
||||||
run: |
|
run: |
|
||||||
@ -42,36 +55,48 @@ 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-test-app-macos:
|
build-apps:
|
||||||
needs: [prepare-json-files]
|
needs: [prepare-files]
|
||||||
runs-on: macos-14
|
strategy:
|
||||||
|
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 }}
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
|
CSC_FOR_PULL_REQUEST: true
|
||||||
|
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
||||||
|
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||||
|
WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
if: github.event_name == 'schedule'
|
name: prepared-files
|
||||||
|
|
||||||
- name: Copy updated .json files
|
- name: Copy prepared files
|
||||||
if: github.event_name == 'schedule'
|
|
||||||
run: |
|
run: |
|
||||||
ls -l artifact
|
ls -R prepared-files
|
||||||
cp artifact/package.json package.json
|
cp prepared-files/package.json package.json
|
||||||
|
cp prepared-files/src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||||
|
mkdir src/wasm-lib/pkg
|
||||||
|
cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
|
||||||
|
|
||||||
- name: Sync node version and setup cache
|
- name: Sync node version and setup cache
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
@ -81,79 +106,10 @@ jobs:
|
|||||||
|
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
|
|
||||||
- name: Setup Rust
|
- run: yarn tronb:vite
|
||||||
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' }}
|
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }}
|
||||||
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
|
||||||
@ -168,7 +124,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' }}
|
if: ${{ env.BUILD_RELEASE == 'true' && matrix.os == 'windows-2022' }}
|
||||||
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
|
||||||
@ -178,83 +134,22 @@ jobs:
|
|||||||
smksp_cert_sync.exe
|
smksp_cert_sync.exe
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
||||||
- name: Build the app for x64
|
- name: Build the app
|
||||||
run: "yarn electron-forge make --arch x64"
|
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
|
||||||
|
|
||||||
- name: Build the app for arm64
|
- name: List artifacts in out/
|
||||||
run: "yarn electron-forge make --arch arm64"
|
run: ls -R out
|
||||||
|
|
||||||
- name: List artifacts
|
|
||||||
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: |
|
|
||||||
signtool.exe sign /sha1 ${{ env.THUMBPRINT }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "${{ env.X64_FILE }}"
|
|
||||||
signtool.exe verify /v /pa "${{ env.X64_FILE }}"
|
|
||||||
signtool.exe sign /sha1 ${{ env.THUMBPRINT }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "${{ env.ARM64_FILE }}"
|
|
||||||
signtool.exe verify /v /pa "${{ env.ARM64_FILE }}"
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: "out/make/*/*/*"
|
name: out-${{ matrix.os }}
|
||||||
|
path: |
|
||||||
# TODO: Run e2e tests
|
out/Zoo*.*
|
||||||
|
out/latest*.yml
|
||||||
|
|
||||||
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: sign the app (and updater bundle potentially)
|
# TODO: add the updater tests back
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
path: "out/make/*/*/*"
|
|
||||||
|
|
||||||
|
|
||||||
publish-apps-release:
|
publish-apps-release:
|
||||||
@ -262,88 +157,76 @@ 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-json-files, build-test-app-macos, build-test-app-windows, build-test-app-ubuntu]
|
needs: [prepare-files, build-apps]
|
||||||
env:
|
env:
|
||||||
VERSION_NO_V: ${{ needs.prepare-json-files.outputs.version }}
|
VERSION_NO_V: ${{ 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 }}
|
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
||||||
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
||||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }}
|
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
||||||
BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }}
|
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
|
||||||
WEBSITE_DIR: ${{ github.event_name == 'release' && 'dl.zoo.dev/releases/modeling-app' || 'dl.zoo.dev/releases/modeling-app/nightly' }}
|
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
|
||||||
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Generate the update static endpoint
|
- uses: actions/download-artifact@v3
|
||||||
run: |
|
with:
|
||||||
ls -l artifact/*/*oo*
|
name: out-windows-2022
|
||||||
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
|
path: out
|
||||||
WINDOWS_X86_64_SIG=`cat artifact/msi/*x64*.msi.zip.sig`
|
|
||||||
WINDOWS_AARCH64_SIG=`cat artifact/msi/*arm64*.msi.zip.sig`
|
- uses: actions/download-artifact@v3
|
||||||
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
with:
|
||||||
jq --null-input \
|
name: out-macos-14
|
||||||
--arg version "${VERSION}" \
|
path: out
|
||||||
--arg pub_date "${PUB_DATE}" \
|
|
||||||
--arg notes "${NOTES}" \
|
- uses: actions/download-artifact@v3
|
||||||
--arg darwin_sig "$DARWIN_SIG" \
|
with:
|
||||||
--arg darwin_url "$RELEASE_DIR/macos/${{ env.URL_CODED_NAME }}.app.tar.gz" \
|
name: out-ubuntu-22.04
|
||||||
--arg windows_x86_64_sig "$WINDOWS_X86_64_SIG" \
|
path: out
|
||||||
--arg windows_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi.zip" \
|
|
||||||
--arg windows_aarch64_sig "$WINDOWS_AARCH64_SIG" \
|
|
||||||
--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": {
|
|
||||||
"darwin-x86_64": {
|
|
||||||
"signature": $darwin_sig,
|
|
||||||
"url": $darwin_url
|
|
||||||
},
|
|
||||||
"darwin-aarch64": {
|
|
||||||
"signature": $darwin_sig,
|
|
||||||
"url": $darwin_url
|
|
||||||
},
|
|
||||||
"windows-x86_64": {
|
|
||||||
"signature": $windows_x86_64_sig,
|
|
||||||
"url": $windows_x86_64_url
|
|
||||||
},
|
|
||||||
"windows-aarch64": {
|
|
||||||
"signature": $windows_aarch64_sig,
|
|
||||||
"url": $windows_aarch64_url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}' > last_update.json
|
|
||||||
cat last_update.json
|
|
||||||
|
|
||||||
- name: Generate the download static endpoint
|
- name: Generate the download static endpoint
|
||||||
run: |
|
run: |
|
||||||
RELEASE_DIR=https://${WEBSITE_DIR}/${VERSION}
|
RELEASE_DIR=https://${WEBSITE_DIR}
|
||||||
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 darwin_url "$RELEASE_DIR/dmg/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_universal.dmg" \
|
--arg mac_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-mac.dmg" \
|
||||||
--arg windows_x86_64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_x64_en-US.msi" \
|
--arg mac_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-mac.dmg" \
|
||||||
--arg windows_aarch64_url "$RELEASE_DIR/msi/${{ env.URL_CODED_NAME }}_${VERSION_NO_V}_arm64_en-US.msi" \
|
--arg windows_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-win.exe" \
|
||||||
|
--arg windows_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x64-win.exe" \
|
||||||
|
--arg linux_arm64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-arm64-linux.AppImage" \
|
||||||
|
--arg linux_x64_url "$RELEASE_DIR/${{ env.URL_CODED_NAME }}-${VERSION_NO_V}-x86_64-linux.AppImage" \
|
||||||
'{
|
'{
|
||||||
"version": $version,
|
"version": $version,
|
||||||
"pub_date": $pub_date,
|
"pub_date": $pub_date,
|
||||||
"notes": $notes,
|
"notes": $notes,
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"dmg-universal": {
|
"dmg-arm64": {
|
||||||
"url": $darwin_url
|
"url": $mac_arm64_url
|
||||||
},
|
},
|
||||||
"msi-x86_64": {
|
"dmg-x64": {
|
||||||
"url": $windows_x86_64_url
|
"url": $mac_x64_url
|
||||||
},
|
},
|
||||||
"msi-aarch64": {
|
"exe-arm64": {
|
||||||
"url": $windows_aarch64_url
|
"url": $windows_arm64_url
|
||||||
|
},
|
||||||
|
"exe-x64": {
|
||||||
|
"url": $windows_x64_url
|
||||||
|
},
|
||||||
|
"appimage-arm64": {
|
||||||
|
"url": $linux_arm64_url
|
||||||
|
},
|
||||||
|
"appimage-x64": {
|
||||||
|
"url": $linux_x64_url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}' > last_download.json
|
}' > last_download.json
|
||||||
cat last_download.json
|
cat last_download.json
|
||||||
|
|
||||||
|
- name: List artifacts
|
||||||
|
run: "ls -R out"
|
||||||
|
|
||||||
- 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.5'
|
||||||
with:
|
with:
|
||||||
@ -352,24 +235,26 @@ jobs:
|
|||||||
- 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: kittycadapi
|
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
|
||||||
|
|
||||||
- name: Upload release files to public bucket
|
- name: Upload release files to public bucket
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.1.3
|
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||||
with:
|
with:
|
||||||
path: artifact
|
path: out
|
||||||
glob: '*/Zoo*'
|
glob: 'Zoo*'
|
||||||
parent: false
|
parent: false
|
||||||
destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
|
|
||||||
- name: Upload update endpoint to public bucket
|
- name: Upload update endpoint to public bucket
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.1.3
|
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||||
with:
|
with:
|
||||||
path: last_update.json
|
path: out
|
||||||
|
glob: 'latest*'
|
||||||
|
parent: false
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
|
|
||||||
- name: Upload download endpoint to public bucket
|
- name: Upload download endpoint to public bucket
|
||||||
uses: google-github-actions/upload-cloud-storage@v2.1.3
|
uses: google-github-actions/upload-cloud-storage@v2.2.0
|
||||||
with:
|
with:
|
||||||
path: last_download.json
|
path: last_download.json
|
||||||
destination: ${{ env.BUCKET_DIR }}
|
destination: ${{ env.BUCKET_DIR }}
|
||||||
@ -378,7 +263,9 @@ jobs:
|
|||||||
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: 'artifact/*/Zoo*'
|
files: 'out/Zoo*'
|
||||||
|
|
||||||
|
# TODO: Add GitHub publisher
|
||||||
|
|
||||||
announce_release:
|
announce_release:
|
||||||
needs: [publish-apps-release]
|
needs: [publish-apps-release]
|
||||||
|
|||||||
2
.github/workflows/build-test-web.yml
vendored
@ -45,7 +45,7 @@ jobs:
|
|||||||
- run: yarn xstate:typegen
|
- run: yarn xstate:typegen
|
||||||
- run: yarn tsc
|
- run: yarn tsc
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: yarn eslint --max-warnings 0 src e2e
|
run: yarn eslint --max-warnings 0 src e2e packages/codemirror-lsp-client
|
||||||
|
|
||||||
|
|
||||||
check-typos:
|
check-typos:
|
||||||
|
|||||||
2
.github/workflows/cargo-check.yml
vendored
@ -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
|
cargo check --all --features disable-println --features pyo3 --features cli
|
||||||
|
|||||||
3
.github/workflows/cargo-clippy.yml
vendored
@ -28,6 +28,7 @@ jobs:
|
|||||||
dir: ['src/wasm-lib']
|
dir: ['src/wasm-lib']
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- uses: taiki-e/install-action@just
|
||||||
- name: Install latest rust
|
- name: Install latest rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
@ -41,7 +42,7 @@ jobs:
|
|||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: |
|
run: |
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo clippy --all --tests --benches -- -D warnings
|
just lint
|
||||||
# If this fails, run "cargo check" to update Cargo.lock,
|
# If this fails, run "cargo check" to update Cargo.lock,
|
||||||
# then add Cargo.lock to the PR.
|
# then add Cargo.lock to the PR.
|
||||||
- name: Check Cargo.lock doesn't need updating
|
- name: Check Cargo.lock doesn't need updating
|
||||||
|
|||||||
5
.github/workflows/cargo-test.yml
vendored
@ -38,11 +38,6 @@ 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
|
||||||
|
|||||||
20
.github/workflows/playwright.yml
vendored
@ -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-ubuntu-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
name: playwright-report-${{ matrix.os }}-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-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
name: playwright-report-${{ matrix.os }}-${{ 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-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
name: test-results-${{ matrix.os }}-${{ 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
|
||||||
@ -245,14 +245,14 @@ jobs:
|
|||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
|
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@ -270,7 +270,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: 30
|
timeout-minutes: 40
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
needs: check-rust-changes
|
needs: check-rust-changes
|
||||||
steps:
|
steps:
|
||||||
@ -359,7 +359,7 @@ jobs:
|
|||||||
if: ${{ !cancelled() && (success() || failure()) }}
|
if: ${{ !cancelled() && (success() || failure()) }}
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
name: test-results-ubuntu-${{ github.sha }}
|
name: test-results-${{ matrix.os }}-${{ github.sha }}
|
||||||
path: test-results/
|
path: test-results/
|
||||||
- name: Run electron tests (with retries)
|
- name: Run electron tests (with retries)
|
||||||
id: retry
|
id: retry
|
||||||
@ -389,7 +389,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 --grep=@electron || true
|
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn playwright test --config=playwright.electron.config.ts --last-failed --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
|
||||||
@ -432,14 +432,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-${{ github.sha }}
|
name: test-results-electron-${{ matrix.os }}-${{ 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-${{ github.sha }}
|
name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }}
|
||||||
path: playwright-report/
|
path: playwright-report/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
6
.gitignore
vendored
@ -54,19 +54,15 @@ 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
|
||||||
.nyc_output/*.vite/
|
.nyc_output/*.vite/
|
||||||
|
|
||||||
# electron
|
# electron
|
||||||
out/
|
out/
|
||||||
|
|
||||||
src-tauri/target
|
|
||||||
electron-test-projects-dir
|
|
||||||
electron-test-projects-dir-2
|
|
||||||
|
|||||||
344
Info.plist
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
<?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>
|
||||||
8
Makefile
@ -7,6 +7,14 @@ 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)'
|
||||||
|
|
||||||
|
|||||||
50
README.md
@ -110,7 +110,6 @@ Which commands from setup are one off vs need to be run every time?
|
|||||||
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
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
|
||||||
```
|
```
|
||||||
@ -189,12 +188,22 @@ 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-1.43.1
|
docker run --network host --rm --init -it playwright/chrome:playwright-x.xx.x
|
||||||
```
|
```
|
||||||
|
|
||||||
and in another terminal, run:
|
and in another terminal, run:
|
||||||
@ -203,21 +212,27 @@ and in another terminal, run:
|
|||||||
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" <test suite>
|
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" <test suite>
|
||||||
```
|
```
|
||||||
|
|
||||||
An example of a `<test suite>` is: `e2e/playwright/flow-tests.spec.ts`
|
|
||||||
|
|
||||||
YOU WILL NEED A PLAYWRIGHT-SECRETS.ENV FILE:
|
#### Specific example
|
||||||
|
|
||||||
|
open a terminal and run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# ./e2e/playwright/playwright-secrets.env
|
docker run --network host --rm --init -it playwright/chrome:playwright-1.46.0
|
||||||
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
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -336,25 +351,6 @@ PS: for the debug panel, the following JSON is useful for snapping the camera
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Tauri e2e tests
|
|
||||||
|
|
||||||
#### Windows (local only until the CI edge version mismatch is fixed)
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn install
|
|
||||||
yarn build:wasm-dev
|
|
||||||
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
|
||||||
yarn vite build --mode development
|
|
||||||
yarn tauri build --debug -b
|
|
||||||
$env:KITTYCAD_API_TOKEN="<YOUR_KITTYCAD_API_TOKEN>"
|
|
||||||
$env:VITE_KC_API_BASE_URL="https://api.dev.zoo.dev"
|
|
||||||
$env:E2E_TAURI_ENABLED="true"
|
|
||||||
$env:TS_NODE_COMPILER_OPTIONS='{"module": "commonjs"}'
|
|
||||||
$env:E2E_APPLICATION=".\src-tauri\target\debug\Zoo Modeling App.exe"
|
|
||||||
Stop-Process -Name msedgedriver
|
|
||||||
yarn wdio run wdio.conf.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
## KCL
|
## KCL
|
||||||
|
|
||||||
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@ -22,8 +22,3 @@ once fixed in engine will just start working here with no language changes.
|
|||||||
|
|
||||||
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||||
chamfer cases work currently.
|
chamfer cases work currently.
|
||||||
|
|
||||||
Sketching on the chamfered face does not currently work.
|
|
||||||
|
|
||||||
- **Shell**: Shell sometimes does not work when arcs or fillets are involved.
|
|
||||||
We are tracking the engine side bug on this.
|
|
||||||
|
|||||||
858
docs/kcl/arrayReduce.md
Normal file
835
docs/kcl/hollow.md
Normal file
@ -19,6 +19,7 @@ layout: manual
|
|||||||
* [`angledLineToX`](kcl/angledLineToX)
|
* [`angledLineToX`](kcl/angledLineToX)
|
||||||
* [`angledLineToY`](kcl/angledLineToY)
|
* [`angledLineToY`](kcl/angledLineToY)
|
||||||
* [`arc`](kcl/arc)
|
* [`arc`](kcl/arc)
|
||||||
|
* [`arrayReduce`](kcl/arrayReduce)
|
||||||
* [`asin`](kcl/asin)
|
* [`asin`](kcl/asin)
|
||||||
* [`assert`](kcl/assert)
|
* [`assert`](kcl/assert)
|
||||||
* [`assertEqual`](kcl/assertEqual)
|
* [`assertEqual`](kcl/assertEqual)
|
||||||
@ -44,6 +45,7 @@ layout: manual
|
|||||||
* [`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)
|
* [`inch`](kcl/inch)
|
||||||
* [`int`](kcl/int)
|
* [`int`](kcl/int)
|
||||||
@ -55,6 +57,7 @@ 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)
|
||||||
@ -62,6 +65,7 @@ layout: manual
|
|||||||
* [`max`](kcl/max)
|
* [`max`](kcl/max)
|
||||||
* [`min`](kcl/min)
|
* [`min`](kcl/min)
|
||||||
* [`mm`](kcl/mm)
|
* [`mm`](kcl/mm)
|
||||||
|
* [`offsetPlane`](kcl/offsetPlane)
|
||||||
* [`patternCircular2d`](kcl/patternCircular2d)
|
* [`patternCircular2d`](kcl/patternCircular2d)
|
||||||
* [`patternCircular3d`](kcl/patternCircular3d)
|
* [`patternCircular3d`](kcl/patternCircular3d)
|
||||||
* [`patternLinear2d`](kcl/patternLinear2d)
|
* [`patternLinear2d`](kcl/patternLinear2d)
|
||||||
@ -87,6 +91,7 @@ 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)
|
||||||
|
|||||||
516
docs/kcl/loft.md
Normal file
138
docs/kcl/offsetPlane.md
Normal file
23507
docs/kcl/std.json
@ -37,8 +37,7 @@ const example = extrude(10, exampleSketch)
|
|||||||
offset: number,
|
offset: number,
|
||||||
// Radius of the arc. Not to be confused with Raiders of the Lost Ark.
|
// Radius of the arc. Not to be confused with Raiders of the Lost Ark.
|
||||||
radius: number,
|
radius: number,
|
||||||
} |
|
}
|
||||||
[number, number]
|
|
||||||
```
|
```
|
||||||
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
|
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
|
||||||
```js
|
```js
|
||||||
|
|||||||
863
docs/kcl/tangentialArcToRelative.md
Normal file
@ -6,7 +6,39 @@ test.afterEach(async ({ page }, testInfo) => {
|
|||||||
await tearDown(page, testInfo)
|
await tearDown(page, testInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Electron user sidebar menu tests', () => {
|
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(
|
test(
|
||||||
'User settings has correct shortcut',
|
'User settings has correct shortcut',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
@ -96,33 +96,49 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
await page.getByTestId('line').click()
|
const btnLine = page.getByTestId('line')
|
||||||
|
const btnLineAriaPressed = await btnLine.getAttribute('aria-pressed')
|
||||||
|
if (btnLineAriaPressed === 'true') {
|
||||||
|
await btnLine.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
|
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(() => u.getGreatestPixDiff(line1, [249, 249, 249]))
|
.poll(async () => 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')) {
|
||||||
expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3)
|
await expect(
|
||||||
|
await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)
|
||||||
|
).toBeLessThan(3)
|
||||||
await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3)
|
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()
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
import {
|
||||||
|
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'
|
import fsp from 'fs/promises'
|
||||||
@ -20,9 +27,19 @@ 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((code) => {
|
await page.addInitScript(() => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem(
|
||||||
}, bracket)
|
'persistCode',
|
||||||
|
`// Extruded Triangle
|
||||||
|
const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([10, 0], %)
|
||||||
|
|> line([-5, 10], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(5, sketch001)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
@ -48,6 +65,8 @@ test.describe('Code pane and errors', () => {
|
|||||||
test('Opening and closing the code pane will consistently show error diagnostics', async ({
|
test('Opening and closing the code pane will consistently show error diagnostics', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
await page.goto('http://localhost:3000')
|
||||||
|
|
||||||
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
|
||||||
@ -73,7 +92,7 @@ test.describe('Code pane and errors', () => {
|
|||||||
|
|
||||||
// Delete a character to break the KCL
|
// Delete a character to break the KCL
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await page.getByText('extrude(').click()
|
await page.getByText('thickness, bracketLeg1Sketch)').click()
|
||||||
await page.keyboard.press('Backspace')
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
// Ensure that a badge appears on the button
|
// Ensure that a badge appears on the button
|
||||||
@ -84,7 +103,7 @@ test.describe('Code pane and errors', () => {
|
|||||||
|
|
||||||
// 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.locator('.cm-tooltip').first()).toBeVisible()
|
||||||
|
|
||||||
// Close the code pane
|
// Close the code pane
|
||||||
await codePaneButton.click()
|
await codePaneButton.click()
|
||||||
@ -107,7 +126,7 @@ test.describe('Code pane and errors', () => {
|
|||||||
|
|
||||||
// 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.locator('.cm-tooltip').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 ({
|
||||||
@ -223,26 +242,24 @@ test(
|
|||||||
'Opening multiple panes persists when switching projects',
|
'Opening multiple panes persists when switching projects',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
// Setup multiple projects.
|
// Setup multiple projects.
|
||||||
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')
|
||||||
|
const bracketDir = join(dir, 'bracket')
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||||
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
|
fsp.mkdir(bracketDir, { recursive: true }),
|
||||||
])
|
])
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
executorInputPath('router-template-slate.kcl'),
|
||||||
`${dir}/router-template-slate/main.kcl`
|
join(routerTemplateDir, 'main.kcl')
|
||||||
),
|
),
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@ -256,10 +273,7 @@ test(
|
|||||||
|
|
||||||
await page.getByText('bracket').click()
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await u.waitForPageLoad()
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// If they're open by default, we're not actually testing anything.
|
// If they're open by default, we're not actually testing anything.
|
||||||
@ -287,16 +301,7 @@ test(
|
|||||||
|
|
||||||
await page.getByText('router-template-slate').click()
|
await page.getByText('router-template-slate').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await u.waitForPageLoad()
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).toBeEnabled({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('All panes opened before should be visible', async () => {
|
await test.step('All panes opened before should be visible', async () => {
|
||||||
|
|||||||
@ -124,7 +124,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('Meta+K')
|
await page.keyboard.press('ControlOrMeta+K')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
await expect(cmdSearchBar).toBeFocused()
|
await expect(cmdSearchBar).toBeFocused()
|
||||||
|
|
||||||
@ -185,7 +185,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('Meta+K')
|
await page.keyboard.press('ControlOrMeta+K')
|
||||||
|
|
||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
@ -250,7 +250,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('Meta+K')
|
await page.keyboard.press('ControlOrMeta+K')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
// Search for extrude command and choose it
|
// Search for extrude command and choose it
|
||||||
|
|||||||
@ -332,7 +332,6 @@ 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(``)
|
||||||
@ -349,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(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
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()
|
||||||
|
|
||||||
@ -368,8 +367,6 @@ 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(``)
|
||||||
@ -382,17 +379,17 @@ test.describe('Copilot ghost text', () => {
|
|||||||
await page.waitForTimeout(800)
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
// Ctrl+z
|
// Ctrl+z
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||||
|
|
||||||
// Ctrl+shift+z
|
// Ctrl+shift+z
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
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"}`)
|
||||||
@ -411,14 +408,14 @@ test.describe('Copilot ghost text', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Once for the enter.
|
// Once for the enter.
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
// Once for the text.
|
// Once for the text.
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { getUtils, setupElectron, tearDown } from './test-utils'
|
import { join } from 'path'
|
||||||
|
import {
|
||||||
|
getUtils,
|
||||||
|
setupElectron,
|
||||||
|
tearDown,
|
||||||
|
executorInputPath,
|
||||||
|
} from './test-utils'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
@ -10,22 +16,19 @@ test(
|
|||||||
'export works on the first try',
|
'export works on the first try',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await Promise.all([fsp.mkdir(`${dir}/bracket`, { recursive: true })])
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
executorInputPath('router-template-slate.kcl'),
|
||||||
`${dir}/bracket/other.kcl`
|
join(bracketDir, 'other.kcl')
|
||||||
),
|
),
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@ -40,12 +43,6 @@ test(
|
|||||||
// open the project
|
// open the project
|
||||||
await page.getByText(`bracket`).click()
|
await page.getByText(`bracket`).click()
|
||||||
|
|
||||||
// wait for the project to load
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// expect zero errors in guter
|
// expect zero errors in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
@ -53,6 +50,12 @@ test(
|
|||||||
const exportButton = page.getByTestId('export-pane-button')
|
const exportButton = page.getByTestId('export-pane-button')
|
||||||
await expect(exportButton).toBeVisible()
|
await expect(exportButton).toBeVisible()
|
||||||
|
|
||||||
|
// Wait for the model to finish loading
|
||||||
|
const modelStateIndicator = page.getByTestId(
|
||||||
|
'model-state-indicator-execution-done'
|
||||||
|
)
|
||||||
|
await expect(modelStateIndicator).toBeVisible({ timeout: 60000 })
|
||||||
|
|
||||||
const gltfOption = page.getByText('glTF')
|
const gltfOption = page.getByText('glTF')
|
||||||
const submitButton = page.getByText('Confirm Export')
|
const submitButton = page.getByText('Confirm Export')
|
||||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||||
@ -101,7 +104,7 @@ test(
|
|||||||
},
|
},
|
||||||
{ timeout: 15_000 }
|
{ timeout: 15_000 }
|
||||||
)
|
)
|
||||||
.toBe(477327)
|
.toBe(477481)
|
||||||
|
|
||||||
// clean up output.gltf
|
// clean up output.gltf
|
||||||
await fsp.rm('output.gltf')
|
await fsp.rm('output.gltf')
|
||||||
|
|||||||
@ -16,7 +16,6 @@ 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()
|
||||||
@ -29,9 +28,9 @@ test.describe('Editor tests', () => {
|
|||||||
|> line([-20, 0], %)
|
|> line([-20, 0], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('/')
|
await page.keyboard.press('/')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XY')
|
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||||
@ -42,9 +41,9 @@ test.describe('Editor tests', () => {
|
|||||||
// |> close(%)`)
|
// |> close(%)`)
|
||||||
|
|
||||||
// uncomment the code
|
// uncomment the code
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('/')
|
await page.keyboard.press('/')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XY')
|
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||||
@ -85,6 +84,63 @@ 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)
|
||||||
|
|
||||||
@ -148,9 +204,7 @@ 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('Control+A')
|
await page.keyboard.press('ControlOrMeta+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(``)
|
||||||
@ -244,6 +298,67 @@ 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 })
|
||||||
@ -402,7 +517,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], %)
|
||||||
|
|||||||
279
e2e/playwright/file-tree.spec.ts
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
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 {
|
||||||
|
openKclCodePanel,
|
||||||
|
openFilePanel,
|
||||||
|
createAndSelectProject,
|
||||||
|
pasteCodeInEditor,
|
||||||
|
createNewFileAndSelect,
|
||||||
|
renameFile,
|
||||||
|
selectFile,
|
||||||
|
editorTextMatches,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
await openKclCodePanel()
|
||||||
|
await openFilePanel()
|
||||||
|
// File the main.kcl with contents
|
||||||
|
const kclCube = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
|
const kcl1 = 'main.kcl'
|
||||||
|
const kcl2 = '2.kcl'
|
||||||
|
|
||||||
|
await createNewFileAndSelect(kcl2)
|
||||||
|
const kclCylinder = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclCylinder)
|
||||||
|
|
||||||
|
await renameFile(kcl2, kcl1)
|
||||||
|
|
||||||
|
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {
|
||||||
|
await selectFile(kcl1)
|
||||||
|
await editorTextMatches(kclCube)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
|
||||||
|
await selectFile(kcl2)
|
||||||
|
await editorTextMatches(kclCylinder)
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'deleting all files recreates a default main.kcl with no code',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
panesOpen,
|
||||||
|
createAndSelectProject,
|
||||||
|
pasteCodeInEditor,
|
||||||
|
deleteFile,
|
||||||
|
editorTextMatches,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await panesOpen(['files', 'code'])
|
||||||
|
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
// File the main.kcl with contents
|
||||||
|
const kclCube = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
|
const kcl1 = 'main.kcl'
|
||||||
|
|
||||||
|
await deleteFile(kcl1)
|
||||||
|
|
||||||
|
await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => {
|
||||||
|
await editorTextMatches('')
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'loading small file, then large, then back to small',
|
||||||
|
{
|
||||||
|
tag: '@electron',
|
||||||
|
},
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
panesOpen,
|
||||||
|
createAndSelectProject,
|
||||||
|
pasteCodeInEditor,
|
||||||
|
createNewFile,
|
||||||
|
openDebugPanel,
|
||||||
|
closeDebugPanel,
|
||||||
|
expectCmdLog,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await panesOpen(['files', 'code'])
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
|
||||||
|
// Create a small file
|
||||||
|
const kclCube = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
// pasted into main.kcl
|
||||||
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
|
// Create a large lego file
|
||||||
|
await createNewFile('lego')
|
||||||
|
const legoFile = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: 'lego.kcl' }),
|
||||||
|
})
|
||||||
|
await expect(legoFile).toBeVisible({ timeout: 60_000 })
|
||||||
|
await legoFile.click()
|
||||||
|
const kclLego = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/lego.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclLego)
|
||||||
|
const mainFile = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: 'main.kcl' }),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Open settings and enable the debug panel
|
||||||
|
await page
|
||||||
|
.getByRole('link', {
|
||||||
|
name: 'settings Settings',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||||
|
await page.getByTestId('settings-close-button').click()
|
||||||
|
|
||||||
|
await test.step('swap between small and large files', async () => {
|
||||||
|
await openDebugPanel()
|
||||||
|
// Previously created a file so we need to start back at main.kcl
|
||||||
|
await mainFile.click()
|
||||||
|
await expectCmdLog('[data-message-type="execution-done"]', 60_000)
|
||||||
|
// Click the large file
|
||||||
|
await legoFile.click()
|
||||||
|
// Once it is building, click back to the smaller file
|
||||||
|
await mainFile.click()
|
||||||
|
await expectCmdLog('[data-message-type="execution-done"]', 60_000)
|
||||||
|
await closeDebugPanel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { setupElectron, tearDown } from './test-utils'
|
import { setupElectron, tearDown, executorInputPath } from './test-utils'
|
||||||
|
import { join } from 'path'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
@ -10,17 +11,14 @@ test(
|
|||||||
'When machine-api server not found butt is disabled and shows the reason',
|
'When machine-api server not found butt is disabled and shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -58,17 +56,14 @@ test(
|
|||||||
'When machine-api server not found home screen & project status shows the reason',
|
'When machine-api server not found home screen & project status shows the reason',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
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 { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
import {
|
||||||
|
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 {
|
||||||
@ -347,17 +354,14 @@ test(
|
|||||||
'Restarting onboarding on desktop takes one attempt',
|
'Restarting onboarding on desktop takes one attempt',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
|
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||||
|
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
executorInputPath('router-template-slate.kcl'),
|
||||||
`${dir}/router-template-slate/main.kcl`
|
join(routerTemplateDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect, Page } from '@playwright/test'
|
||||||
import {
|
import {
|
||||||
doExport,
|
doExport,
|
||||||
|
executorInputPath,
|
||||||
getUtils,
|
getUtils,
|
||||||
isOutOfViewInScrollContainer,
|
isOutOfViewInScrollContainer,
|
||||||
Paths,
|
Paths,
|
||||||
setupElectron,
|
setupElectron,
|
||||||
tearDown,
|
tearDown,
|
||||||
|
createProjectAndRenameIt,
|
||||||
} from './test-utils'
|
} from './test-utils'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
@ -45,17 +47,14 @@ test(
|
|||||||
'click help/keybindings from project page',
|
'click help/keybindings from project page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -64,8 +63,6 @@ test(
|
|||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
page.on('console', console.log)
|
|
||||||
|
|
||||||
// expect to see the text bracket
|
// expect to see the text bracket
|
||||||
await expect(page.getByText('bracket')).toBeVisible()
|
await expect(page.getByText('bracket')).toBeVisible()
|
||||||
|
|
||||||
@ -92,17 +89,13 @@ test(
|
|||||||
'when code with error first loads you get errors in console',
|
'when code with error first loads you get errors in console',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/broken-code`, { recursive: true })
|
await fsp.mkdir(join(dir, 'broken-code'), { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/broken-code-test.kcl',
|
executorInputPath('broken-code-test.kcl'),
|
||||||
`${dir}/broken-code/main.kcl`
|
join(dir, 'broken-code', 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -138,17 +131,14 @@ test.describe('Can export from electron app', () => {
|
|||||||
`Can export using ${method}`,
|
`Can export using ${method}`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -157,9 +147,6 @@ test.describe('Can export from electron app', () => {
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
await electronApp.context().addInitScript(async () => {
|
|
||||||
;(window as any).playwrightSkipFilePicker = true
|
|
||||||
})
|
|
||||||
|
|
||||||
const pointOnModel = { x: 630, y: 280 }
|
const pointOnModel = { x: 630, y: 280 }
|
||||||
|
|
||||||
@ -183,10 +170,10 @@ test.describe('Can export from electron app', () => {
|
|||||||
// gray at this pixel means the stream has loaded in the most
|
// gray at this pixel means the stream has loaded in the most
|
||||||
// user way we can verify it (pixel color)
|
// user way we can verify it (pixel color)
|
||||||
await expect
|
await expect
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [75, 75, 75]), {
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
.toBeLessThan(10)
|
.toBeLessThan(15)
|
||||||
})
|
})
|
||||||
|
|
||||||
const exportLocations: Array<Paths> = []
|
const exportLocations: Array<Paths> = []
|
||||||
@ -217,7 +204,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
},
|
},
|
||||||
{ timeout: 15_000 }
|
{ timeout: 15_000 }
|
||||||
)
|
)
|
||||||
.toBe(477327)
|
.toBe(477481)
|
||||||
|
|
||||||
// clean up output.gltf
|
// clean up output.gltf
|
||||||
await fsp.rm('output.gltf')
|
await fsp.rm('output.gltf')
|
||||||
@ -232,10 +219,6 @@ test(
|
|||||||
'Rename and delete projects, also spam arrow keys when renaming',
|
'Rename and delete projects, also spam arrow keys when renaming',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
@ -468,7 +451,8 @@ test(
|
|||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
test.fixme(
|
|
||||||
|
test(
|
||||||
'File in the file pane should open with a single click',
|
'File in the file pane should open with a single click',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
@ -508,10 +492,6 @@ test.fixme(
|
|||||||
|
|
||||||
await file.click()
|
await file.click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
await expect(u.codeLocator).toContainText(
|
await expect(u.codeLocator).toContainText(
|
||||||
'A mounting bracket for the Focusrite Scarlett Solo audio interface'
|
'A mounting bracket for the Focusrite Scarlett Solo audio interface'
|
||||||
)
|
)
|
||||||
@ -520,14 +500,73 @@ test.fixme(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Nested directories in project without main.kcl do not create main.kcl',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
let testDir: string | undefined
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
await fsp.mkdir(join(dir, 'router-template-slate', 'nested'), {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('router-template-slate.kcl'),
|
||||||
|
join(dir, 'router-template-slate', 'nested', 'slate.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
|
join(dir, 'router-template-slate', 'nested', 'bracket.kcl')
|
||||||
|
)
|
||||||
|
testDir = dir
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await test.step('Open the project', async () => {
|
||||||
|
await page.getByText('router-template-slate').click()
|
||||||
|
await expect(page.getByTestId('loading')).toBeAttached()
|
||||||
|
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||||
|
timeout: 20_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// It actually loads.
|
||||||
|
await expect(u.codeLocator).toContainText('mounting bracket')
|
||||||
|
await expect(u.codeLocator).toContainText('const radius =')
|
||||||
|
})
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
|
||||||
|
// Find the current file.
|
||||||
|
const filesPane = page.locator('#files-pane')
|
||||||
|
await expect(filesPane.getByText('bracket.kcl')).toBeVisible()
|
||||||
|
// But there's no main.kcl in the file tree browser.
|
||||||
|
await expect(filesPane.getByText('main.kcl')).not.toBeVisible()
|
||||||
|
// No main.kcl file is created on the filesystem.
|
||||||
|
expect(testDir).toBeDefined()
|
||||||
|
if (testDir !== undefined) {
|
||||||
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
await expect(
|
||||||
|
fsp.access(join(testDir, 'router-template-slate', 'main.kcl'))
|
||||||
|
).rejects.toThrow()
|
||||||
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
await expect(
|
||||||
|
fsp.access(join(testDir, 'router-template-slate', 'nested', 'main.kcl'))
|
||||||
|
).rejects.toThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
})
|
})
|
||||||
@ -535,33 +574,23 @@ test(
|
|||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
const createProjectAndRenameIt = async (name: string) =>
|
const createProjectAndRenameItTest = async ({
|
||||||
test.step(`Create and rename project ${name}`, async () => {
|
name,
|
||||||
await page.getByRole('button', { name: 'New project' }).click()
|
page,
|
||||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
}: {
|
||||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
name: string
|
||||||
|
page: Page
|
||||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
}) => {
|
||||||
await page.getByText(`project-000`).hover()
|
await test.step(`Create and rename project ${name}`, async () => {
|
||||||
await page.getByText(`project-000`).focus()
|
await createProjectAndRenameIt({ name, page })
|
||||||
|
|
||||||
await page.getByLabel('sketch').first().click()
|
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// type "updated project name"
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await page.keyboard.type(name)
|
|
||||||
|
|
||||||
await page.getByLabel('checkmark').last().click()
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// we need to create the folders so that the order is correct
|
// we need to create the folders so that the order is correct
|
||||||
// creating them ahead of time with fs tools means they all have the same timestamp
|
// creating them ahead of time with fs tools means they all have the same timestamp
|
||||||
await createProjectAndRenameIt('router-template-slate')
|
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
|
||||||
// await createProjectAndRenameIt('focusrite_scarlett_mounting_braket')
|
await createProjectAndRenameItTest({ name: 'bracket', page })
|
||||||
await createProjectAndRenameIt('bracket')
|
await createProjectAndRenameItTest({ name: 'lego', page })
|
||||||
await createProjectAndRenameIt('lego')
|
|
||||||
|
|
||||||
await test.step('delete the middle project, i.e. the bracket project', async () => {
|
await test.step('delete the middle project, i.e. the bracket project', async () => {
|
||||||
const project = page.getByText('bracket')
|
const project = page.getByText('bracket')
|
||||||
@ -618,14 +647,52 @@ test(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Can load a file with CRLF line endings',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||||
|
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||||
|
|
||||||
|
const file = await fsp.readFile(
|
||||||
|
executorInputPath('router-template-slate.kcl'),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
// Replace both \r optionally so we don't end up with \r\r\n
|
||||||
|
const fileWithCRLF = file.replace(/\r?\n/g, '\r\n')
|
||||||
|
await fsp.writeFile(
|
||||||
|
join(routerTemplateDir, 'main.kcl'),
|
||||||
|
fileWithCRLF,
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await page.getByText('router-template-slate').click()
|
||||||
|
await expect(page.getByTestId('loading')).toBeAttached()
|
||||||
|
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||||
|
timeout: 20_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(u.codeLocator).toContainText('routerDiameter')
|
||||||
|
await expect(u.codeLocator).toContainText('templateGap')
|
||||||
|
await expect(u.codeLocator).toContainText('minClampingDistance')
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Can sort projects on home page',
|
'Can sort projects on home page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
})
|
})
|
||||||
@ -635,33 +702,23 @@ test(
|
|||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
const createProjectAndRenameIt = async (name: string) =>
|
const createProjectAndRenameItTest = async ({
|
||||||
test.step(`Create and rename project ${name}`, async () => {
|
name,
|
||||||
await page.getByRole('button', { name: 'New project' }).click()
|
page,
|
||||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
}: {
|
||||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
name: string
|
||||||
|
page: Page
|
||||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
}) => {
|
||||||
await page.getByText(`project-000`).hover()
|
await test.step(`Create and rename project ${name}`, async () => {
|
||||||
await page.getByText(`project-000`).focus()
|
await createProjectAndRenameIt({ name, page })
|
||||||
|
|
||||||
await page.getByLabel('sketch').first().click()
|
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// type "updated project name"
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await page.keyboard.type(name)
|
|
||||||
|
|
||||||
await page.getByLabel('checkmark').last().click()
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// we need to create the folders so that the order is correct
|
// we need to create the folders so that the order is correct
|
||||||
// creating them ahead of time with fs tools means they all have the same timestamp
|
// creating them ahead of time with fs tools means they all have the same timestamp
|
||||||
await createProjectAndRenameIt('router-template-slate')
|
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
|
||||||
// await createProjectAndRenameIt('focusrite_scarlett_mounting_braket')
|
await createProjectAndRenameItTest({ name: 'bracket', page })
|
||||||
await createProjectAndRenameIt('bracket')
|
await createProjectAndRenameItTest({ name: 'lego', page })
|
||||||
await createProjectAndRenameIt('lego')
|
|
||||||
|
|
||||||
await test.step('should be shorted by modified initially', async () => {
|
await test.step('should be shorted by modified initially', async () => {
|
||||||
const lastModifiedButton = page.getByRole('button', {
|
const lastModifiedButton = page.getByRole('button', {
|
||||||
@ -748,10 +805,6 @@ test(
|
|||||||
'When the project folder is empty, user can create new project and open it.',
|
'When the project folder is empty, user can create new project and open it.',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({ testInfo })
|
const { electronApp, page } = await setupElectron({ testInfo })
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
@ -796,10 +849,10 @@ const extrude001 = extrude(200, sketch001)`)
|
|||||||
// gray at this pixel means the stream has loaded in the most
|
// gray at this pixel means the stream has loaded in the most
|
||||||
// user way we can verify it (pixel color)
|
// user way we can verify it (pixel color)
|
||||||
await expect
|
await expect
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [132, 132, 132]), {
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [143, 143, 143]), {
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
.toBeLessThan(10)
|
.toBeLessThan(15)
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await page.mouse.move(0, 0, { steps: 5 })
|
await page.mouse.move(0, 0, { steps: 5 })
|
||||||
@ -807,8 +860,8 @@ const extrude001 = extrude(200, sketch001)`)
|
|||||||
await page.mouse.click(pointOnModel.x, pointOnModel.y)
|
await page.mouse.click(pointOnModel.x, pointOnModel.y)
|
||||||
// check user can interact with model by checking it turns yellow
|
// check user can interact with model by checking it turns yellow
|
||||||
await expect
|
await expect
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [176, 180, 132]))
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [180, 180, 137]))
|
||||||
.toBeLessThan(10)
|
.toBeLessThan(15)
|
||||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||||
|
|
||||||
await page.getByTestId('app-logo').click()
|
await page.getByTestId('app-logo').click()
|
||||||
@ -836,25 +889,35 @@ test(
|
|||||||
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
fsp.mkdir(join(dir, 'router-template-slate'), { recursive: true }),
|
||||||
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
|
fsp.mkdir(join(dir, 'bracket'), { recursive: true }),
|
||||||
])
|
])
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
join(
|
||||||
`${dir}/router-template-slate/main.kcl`
|
'src',
|
||||||
|
'wasm-lib',
|
||||||
|
'tests',
|
||||||
|
'executor',
|
||||||
|
'inputs',
|
||||||
|
'router-template-slate.kcl'
|
||||||
|
),
|
||||||
|
join(dir, 'router-template-slate', 'main.kcl')
|
||||||
),
|
),
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
join(
|
||||||
`${dir}/bracket/main.kcl`
|
'src',
|
||||||
|
'wasm-lib',
|
||||||
|
'tests',
|
||||||
|
'executor',
|
||||||
|
'inputs',
|
||||||
|
'focusrite_scarlett_mounting_braket.kcl'
|
||||||
|
),
|
||||||
|
join(dir, 'bracket', 'main.kcl')
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@ -872,24 +935,15 @@ test(
|
|||||||
|
|
||||||
await page.getByText('bracket').click()
|
await page.getByText('bracket').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await u.waitForPageLoad()
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).toBeEnabled({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// gray at this pixel means the stream has loaded in the most
|
// gray at this pixel means the stream has loaded in the most
|
||||||
// user way we can verify it (pixel color)
|
// user way we can verify it (pixel color)
|
||||||
await expect
|
await expect
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [75, 75, 75]), {
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
.toBeLessThan(10)
|
.toBeLessThan(15)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||||
@ -906,24 +960,15 @@ test(
|
|||||||
|
|
||||||
await page.getByText('router-template-slate').click()
|
await page.getByText('router-template-slate').click()
|
||||||
|
|
||||||
await expect(page.getByTestId('loading')).toBeAttached()
|
await u.waitForPageLoad()
|
||||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).toBeEnabled({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// gray at this pixel means the stream has loaded in the most
|
// gray at this pixel means the stream has loaded in the most
|
||||||
// user way we can verify it (pixel color)
|
// user way we can verify it (pixel color)
|
||||||
await expect
|
await expect
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [132, 132, 132]), {
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [143, 143, 143]), {
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
.toBeLessThan(10)
|
.toBeLessThan(15)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Opening the router-template project should load the stream', async () => {
|
await test.step('Opening the router-template project should load the stream', async () => {
|
||||||
@ -1053,10 +1098,6 @@ test(
|
|||||||
'Search projects on desktop home',
|
'Search projects on desktop home',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName: _ }, testInfo) => {
|
async ({ browserName: _ }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const projectData = [
|
const projectData = [
|
||||||
['basic bracket', 'focusrite_scarlett_mounting_braket.kcl'],
|
['basic bracket', 'focusrite_scarlett_mounting_braket.kcl'],
|
||||||
['basic-cube', 'basic_fillet_cube_end.kcl'],
|
['basic-cube', 'basic_fillet_cube_end.kcl'],
|
||||||
@ -1071,7 +1112,7 @@ test(
|
|||||||
for (const [name, file] of projectData) {
|
for (const [name, file] of projectData) {
|
||||||
await fsp.mkdir(join(dir, name), { recursive: true })
|
await fsp.mkdir(join(dir, name), { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join('src', 'wasm-lib', 'tests', 'executor', 'inputs', file),
|
executorInputPath(file),
|
||||||
join(dir, name, `main.kcl`)
|
join(dir, name, `main.kcl`)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1118,14 +1159,11 @@ test(
|
|||||||
'file pane is scrollable when there are many files',
|
'file pane is scrollable when there are many files',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/testProject`, { recursive: true })
|
const testDir = join(dir, 'testProject')
|
||||||
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
const fileNames = [
|
const fileNames = [
|
||||||
'angled_line.kcl',
|
'angled_line.kcl',
|
||||||
'basic_fillet_cube_close_opposite.kcl',
|
'basic_fillet_cube_close_opposite.kcl',
|
||||||
@ -1189,8 +1227,8 @@ test(
|
|||||||
]
|
]
|
||||||
for (const fileName of fileNames) {
|
for (const fileName of fileNames) {
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
`src/wasm-lib/tests/executor/inputs/${fileName}`,
|
executorInputPath(fileName),
|
||||||
`${dir}/testProject/${fileName}`
|
join(testDir, fileName)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1231,19 +1269,16 @@ test(
|
|||||||
'select all in code editor does not actually select all, just what is visible (regression)',
|
'select all in code editor does not actually select all, just what is visible (regression)',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
// src/wasm-lib/tests/executor/inputs/mike_stress_test.kcl
|
// src/wasm-lib/tests/executor/inputs/mike_stress_test.kcl
|
||||||
const name = 'mike_stress_test'
|
const name = 'mike_stress_test'
|
||||||
await fsp.mkdir(`${dir}/${name}`, { recursive: true })
|
const testDir = join(dir, name)
|
||||||
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
`src/wasm-lib/tests/executor/inputs/${name}.kcl`,
|
executorInputPath(`${name}.kcl`),
|
||||||
`${dir}/${name}/main.kcl`
|
join(testDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -1254,18 +1289,13 @@ test(
|
|||||||
|
|
||||||
await page.getByText('mike_stress_test').click()
|
await page.getByText('mike_stress_test').click()
|
||||||
|
|
||||||
const modifier =
|
|
||||||
process.platform === 'win32' || process.platform === 'linux'
|
|
||||||
? 'Control'
|
|
||||||
: 'Meta'
|
|
||||||
|
|
||||||
await test.step('select all in code editor, check its length', async () => {
|
await test.step('select all in code editor, check its length', async () => {
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
// expect u.codeLocator to have some text
|
// expect u.codeLocator to have some text
|
||||||
await expect(u.codeLocator).toContainText('line(')
|
await expect(u.codeLocator).toContainText('line(')
|
||||||
await page.keyboard.down(modifier)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyA')
|
await page.keyboard.press('KeyA')
|
||||||
await page.keyboard.up(modifier)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
// check the length of the selected text
|
// check the length of the selected text
|
||||||
const selectedText = await page.evaluate(() => {
|
const selectedText = await page.evaluate(() => {
|
||||||
@ -1281,9 +1311,9 @@ test(
|
|||||||
await test.step('delete all the text, select again and verify there are no characters left', async () => {
|
await test.step('delete all the text, select again and verify there are no characters left', async () => {
|
||||||
await page.keyboard.press('Backspace')
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
await page.keyboard.down(modifier)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyA')
|
await page.keyboard.press('KeyA')
|
||||||
await page.keyboard.up(modifier)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
// check the length of the selected text
|
// check the length of the selected text
|
||||||
const selectedText = await page.evaluate(() => {
|
const selectedText = await page.evaluate(() => {
|
||||||
@ -1302,10 +1332,6 @@ test(
|
|||||||
'Settings persist across restarts',
|
'Settings persist across restarts',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
await test.step('We can change a user setting like theme', async () => {
|
await test.step('We can change a user setting like theme', async () => {
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
@ -1350,27 +1376,16 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
'A file you have open',
|
'A file you have open',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
test.skip(
|
const { electronApp, page, dir } = await setupElectron({
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
const exampleDir = join(
|
|
||||||
'src',
|
|
||||||
'wasm-lib',
|
|
||||||
'tests',
|
|
||||||
'executor',
|
|
||||||
'inputs'
|
|
||||||
)
|
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
join(dir, 'Test Project', 'main.kcl')
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
)
|
)
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'cylinder.kcl'),
|
executorInputPath('cylinder.kcl'),
|
||||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1382,6 +1397,16 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
// Constants and locators
|
// Constants and locators
|
||||||
const projectLink = page.getByText('Test Project')
|
const projectLink = page.getByText('Test Project')
|
||||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
|
const checkUnRenamedFS = () => {
|
||||||
|
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||||
|
return fs.existsSync(filePath)
|
||||||
|
}
|
||||||
|
const newFileName = 'newFileName'
|
||||||
|
const checkRenamedFS = () => {
|
||||||
|
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||||
|
return fs.existsSync(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
const fileToRename = page
|
const fileToRename = page
|
||||||
.getByRole('listitem')
|
.getByRole('listitem')
|
||||||
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||||
@ -1390,7 +1415,6 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
.filter({ has: page.getByRole('button', { name: 'newFileName.kcl' }) })
|
.filter({ has: page.getByRole('button', { name: 'newFileName.kcl' }) })
|
||||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||||
const newFileName = 'newFileName'
|
|
||||||
const codeLocator = page.locator('.cm-content')
|
const codeLocator = page.locator('.cm-content')
|
||||||
|
|
||||||
await test.step('Open project and file pane', async () => {
|
await test.step('Open project and file pane', async () => {
|
||||||
@ -1401,6 +1425,8 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(fileToRename).toBeVisible()
|
await expect(fileToRename).toBeVisible()
|
||||||
|
expect(checkUnRenamedFS()).toBeTruthy()
|
||||||
|
expect(checkRenamedFS()).toBeFalsy()
|
||||||
await fileToRename.click()
|
await fileToRename.click()
|
||||||
await expect(projectMenuButton).toContainText('fileToRename.kcl')
|
await expect(projectMenuButton).toContainText('fileToRename.kcl')
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
@ -1419,6 +1445,8 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
await test.step('Verify the file is renamed', async () => {
|
await test.step('Verify the file is renamed', async () => {
|
||||||
await expect(fileToRename).not.toBeAttached()
|
await expect(fileToRename).not.toBeAttached()
|
||||||
await expect(renamedFile).toBeVisible()
|
await expect(renamedFile).toBeVisible()
|
||||||
|
expect(checkUnRenamedFS()).toBeFalsy()
|
||||||
|
expect(checkRenamedFS()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Verify we navigated', async () => {
|
await test.step('Verify we navigated', async () => {
|
||||||
@ -1442,27 +1470,16 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
'A file you do not have open',
|
'A file you do not have open',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
test.skip(
|
const { electronApp, page, dir } = await setupElectron({
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
const exampleDir = join(
|
|
||||||
'src',
|
|
||||||
'wasm-lib',
|
|
||||||
'tests',
|
|
||||||
'executor',
|
|
||||||
'inputs'
|
|
||||||
)
|
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
join(dir, 'Test Project', 'main.kcl')
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
)
|
)
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'cylinder.kcl'),
|
executorInputPath('cylinder.kcl'),
|
||||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1473,6 +1490,14 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
|
|
||||||
// Constants and locators
|
// Constants and locators
|
||||||
const newFileName = 'newFileName'
|
const newFileName = 'newFileName'
|
||||||
|
const checkUnRenamedFS = () => {
|
||||||
|
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||||
|
return fs.existsSync(filePath)
|
||||||
|
}
|
||||||
|
const checkRenamedFS = () => {
|
||||||
|
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||||
|
return fs.existsSync(filePath)
|
||||||
|
}
|
||||||
const projectLink = page.getByText('Test Project')
|
const projectLink = page.getByText('Test Project')
|
||||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
const fileToRename = page
|
const fileToRename = page
|
||||||
@ -1493,6 +1518,8 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(fileToRename).toBeVisible()
|
await expect(fileToRename).toBeVisible()
|
||||||
|
expect(checkUnRenamedFS()).toBeTruthy()
|
||||||
|
expect(checkRenamedFS()).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Rename the file', async () => {
|
await test.step('Rename the file', async () => {
|
||||||
@ -1506,6 +1533,8 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
await test.step('Verify the file is renamed', async () => {
|
await test.step('Verify the file is renamed', async () => {
|
||||||
await expect(fileToRename).not.toBeAttached()
|
await expect(fileToRename).not.toBeAttached()
|
||||||
await expect(renamedFile).toBeVisible()
|
await expect(renamedFile).toBeVisible()
|
||||||
|
expect(checkUnRenamedFS()).toBeFalsy()
|
||||||
|
expect(checkRenamedFS()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Verify we have not navigated', async () => {
|
await test.step('Verify we have not navigated', async () => {
|
||||||
@ -1532,30 +1561,19 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
`A folder you're not inside`,
|
`A folder you're not inside`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
test.skip(
|
const { electronApp, page, dir } = await setupElectron({
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
})
|
})
|
||||||
const exampleDir = join(
|
|
||||||
'src',
|
|
||||||
'wasm-lib',
|
|
||||||
'tests',
|
|
||||||
'executor',
|
|
||||||
'inputs'
|
|
||||||
)
|
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
join(dir, 'Test Project', 'main.kcl')
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
)
|
)
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'cylinder.kcl'),
|
executorInputPath('cylinder.kcl'),
|
||||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1573,8 +1591,17 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
})
|
})
|
||||||
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||||
const renameInput = page.getByPlaceholder('folderToRename')
|
const originalFolderName = 'folderToRename'
|
||||||
|
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||||
const newFolderName = 'newFolderName'
|
const newFolderName = 'newFolderName'
|
||||||
|
const checkUnRenamedFolderFS = () => {
|
||||||
|
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||||
|
return fs.existsSync(folderPath)
|
||||||
|
}
|
||||||
|
const checkRenamedFolderFS = () => {
|
||||||
|
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||||
|
return fs.existsSync(folderPath)
|
||||||
|
}
|
||||||
|
|
||||||
await test.step('Open project and file pane', async () => {
|
await test.step('Open project and file pane', async () => {
|
||||||
await expect(projectLink).toBeVisible()
|
await expect(projectLink).toBeVisible()
|
||||||
@ -1588,6 +1615,8 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(folderToRename).toBeVisible()
|
await expect(folderToRename).toBeVisible()
|
||||||
|
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||||
|
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Rename the folder', async () => {
|
await test.step('Rename the folder', async () => {
|
||||||
@ -1607,6 +1636,8 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
await expect(projectMenuButton).toContainText('main.kcl')
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
await expect(renamedFolder).toBeVisible()
|
await expect(renamedFolder).toBeVisible()
|
||||||
await expect(folderToRename).not.toBeAttached()
|
await expect(folderToRename).not.toBeAttached()
|
||||||
|
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||||
|
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
@ -1617,12 +1648,7 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
`A folder you are inside`,
|
`A folder you are inside`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
test.skip(
|
const { electronApp, page, dir } = await setupElectron({
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const exampleDir = join('src', 'wasm-lib', 'tests', 'executor', 'inputs')
|
|
||||||
const { electronApp, page } = await setupElectron({
|
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
@ -1630,11 +1656,11 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
recursive: true,
|
recursive: true,
|
||||||
})
|
})
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
join(dir, 'Test Project', 'main.kcl')
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
)
|
)
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
join(exampleDir, 'cylinder.kcl'),
|
executorInputPath('cylinder.kcl'),
|
||||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1655,8 +1681,17 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||||
})
|
})
|
||||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||||
const renameInput = page.getByPlaceholder('folderToRename')
|
const originalFolderName = 'folderToRename'
|
||||||
|
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||||
const newFolderName = 'newFolderName'
|
const newFolderName = 'newFolderName'
|
||||||
|
const checkUnRenamedFolderFS = () => {
|
||||||
|
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||||
|
return fs.existsSync(folderPath)
|
||||||
|
}
|
||||||
|
const checkRenamedFolderFS = () => {
|
||||||
|
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||||
|
return fs.existsSync(folderPath)
|
||||||
|
}
|
||||||
|
|
||||||
await test.step('Open project and navigate into folder', async () => {
|
await test.step('Open project and navigate into folder', async () => {
|
||||||
await expect(projectLink).toBeVisible()
|
await expect(projectLink).toBeVisible()
|
||||||
@ -1679,9 +1714,12 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
expect(newUrl).toContain('folderToRename')
|
expect(newUrl).toContain('folderToRename')
|
||||||
expect(newUrl).toContain('someFileWithin.kcl')
|
expect(newUrl).toContain('someFileWithin.kcl')
|
||||||
expect(newUrl).not.toContain('main.kcl')
|
expect(newUrl).not.toContain('main.kcl')
|
||||||
|
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||||
|
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Rename the folder', async () => {
|
await test.step('Rename the folder', async () => {
|
||||||
|
await page.waitForTimeout(2000)
|
||||||
await folderToRename.click({ button: 'right' })
|
await folderToRename.click({ button: 'right' })
|
||||||
await expect(renameMenuItem).toBeVisible()
|
await expect(renameMenuItem).toBeVisible()
|
||||||
await renameMenuItem.click()
|
await renameMenuItem.click()
|
||||||
@ -1704,9 +1742,124 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
expect(url).not.toContain('main.kcl')
|
expect(url).not.toContain('main.kcl')
|
||||||
expect(url).toContain(newFolderName)
|
expect(url).toContain(newFolderName)
|
||||||
expect(url).toContain('someFileWithin.kcl')
|
expect(url).toContain('someFileWithin.kcl')
|
||||||
|
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||||
|
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test.describe('Deleting files from the file pane', () => {
|
||||||
|
test(
|
||||||
|
`when main.kcl exists, navigate to main.kcl`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
const testDir = join(dir, 'testProject')
|
||||||
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
join(testDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
|
join(testDir, 'fileToDelete.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const projectCard = page.getByText('testProject')
|
||||||
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
|
const fileToDelete = page
|
||||||
|
.getByRole('listitem')
|
||||||
|
.filter({ has: page.getByRole('button', { name: 'fileToDelete.kcl' }) })
|
||||||
|
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||||
|
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||||
|
|
||||||
|
await test.step('Open project and navigate to fileToDelete.kcl', async () => {
|
||||||
|
await projectCard.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await u.openFilePanel()
|
||||||
|
|
||||||
|
await fileToDelete.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||||
|
await u.closeKclCodePanel()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete fileToDelete.kcl', async () => {
|
||||||
|
await fileToDelete.click({ button: 'right' })
|
||||||
|
await expect(deleteMenuItem).toBeVisible()
|
||||||
|
await deleteMenuItem.click()
|
||||||
|
await expect(deleteConfirmation).toBeVisible()
|
||||||
|
await deleteConfirmation.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Check deletion and navigation', async () => {
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await expect(fileToDelete).not.toBeVisible()
|
||||||
|
await u.closeFilePanel()
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await expect(u.codeLocator).toContainText('circle(')
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test.fixme('TODO - when main.kcl does not exist', async () => {})
|
||||||
|
})
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Original project name persist after onboarding',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
const getAllProjects = () => page.getByTestId('project-link').all()
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await test.step('Should create and name a project called wrist brace', async () => {
|
||||||
|
await createProjectAndRenameIt({ name: 'wrist brace', page })
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Should go through onboarding', async () => {
|
||||||
|
await page.getByTestId('user-sidebar-toggle').click()
|
||||||
|
await page.getByTestId('user-settings').click()
|
||||||
|
await page.getByRole('button', { name: 'Replay Onboarding' }).click()
|
||||||
|
|
||||||
|
const numberOfOnboardingSteps = 12
|
||||||
|
for (let clicks = 0; clicks < numberOfOnboardingSteps; clicks++) {
|
||||||
|
await page.getByTestId('onboarding-next').click()
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByTestId('project-sidebar-toggle').click()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Should go home after onboarding is completed', async () => {
|
||||||
|
await page.getByRole('button', { name: 'Go to Home' }).click()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Should show the original project called wrist brace', async () => {
|
||||||
|
const projectNames = ['Tutorial Project 00', 'wrist brace']
|
||||||
|
for (const [index, projectLink] of (await getAllProjects()).entries()) {
|
||||||
|
await expect(projectLink).toContainText(projectNames[index])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
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 { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
import {
|
||||||
|
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'
|
||||||
|
|
||||||
@ -47,6 +54,67 @@ const sketch001 = startSketchAt([-0, -0])
|
|||||||
const crypticErrorText = `ApiError`
|
const crypticErrorText = `ApiError`
|
||||||
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
||||||
})
|
})
|
||||||
|
test('user should not have to press down twice in cmdbar', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// because the model has `line([0,0]..` it is valid code, but the model is invalid
|
||||||
|
// regression test for https://github.com/KittyCAD/modeling-app/issues/3251
|
||||||
|
// Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch2 = startSketchOn("XY")
|
||||||
|
const sketch001 = startSketchAt([-0, -0])
|
||||||
|
|> line([0, 0], %)
|
||||||
|
|> line([-4.84, -5.29], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
await test.step('Check arrow down works', async () => {
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('option', { name: 'floppy disk arrow Export' })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
// press arrow down key twice
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
|
||||||
|
// STL is the third option, which makes sense for two arrow downs
|
||||||
|
await expect(page.locator('[data-headlessui-state="active"]')).toHaveText(
|
||||||
|
'STL'
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Check arrow up works', async () => {
|
||||||
|
// theme in test is dark, which is the second option, which means we can test arrow up
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
await page.getByText('The overall appearance of the').click()
|
||||||
|
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
await expect(page.locator('[data-headlessui-state="active"]')).toHaveText(
|
||||||
|
'light'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
test('executes on load', async ({ page }) => {
|
test('executes on load', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -351,6 +419,7 @@ 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,
|
||||||
@ -386,20 +455,22 @@ 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 expect(exportingToastMessage).toBeVisible()
|
await Promise.all([
|
||||||
await expect(alreadyExportingToastMessage).toBeVisible()
|
expect(exportingToastMessage.first()).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 expect(exportingToastMessage).not.toBeVisible()
|
await Promise.all([
|
||||||
await expect(errorToastMessage).not.toBeVisible()
|
expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }),
|
||||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
expect(errorToastMessage).not.toBeVisible(),
|
||||||
|
expect(engineErrorToastMessage).not.toBeVisible(),
|
||||||
await expect(successToastMessage).toBeVisible()
|
expect(successToastMessage).toBeVisible({ timeout: 15_000 }),
|
||||||
|
expect(alreadyExportingToastMessage).not.toBeVisible({
|
||||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
timeout: 15_000,
|
||||||
|
}),
|
||||||
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -412,10 +483,12 @@ 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()
|
await Promise.all([
|
||||||
await expect(errorToastMessage).not.toBeVisible()
|
expect(exportingToastMessage).not.toBeVisible(),
|
||||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
expect(errorToastMessage).not.toBeVisible(),
|
||||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
expect(engineErrorToastMessage).not.toBeVisible(),
|
||||||
|
expect(alreadyExportingToastMessage).not.toBeVisible(),
|
||||||
|
])
|
||||||
|
|
||||||
await expect(successToastMessage).toBeVisible()
|
await expect(successToastMessage).toBeVisible()
|
||||||
})
|
})
|
||||||
@ -425,17 +498,14 @@ const sketch001 = startSketchAt([-0, -0])
|
|||||||
`Network health indicator only appears in modeling view`,
|
`Network health indicator only appears in modeling view`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName: _ }, testInfo) => {
|
async ({ browserName: _ }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 32 KiB |
@ -2,13 +2,13 @@ import {
|
|||||||
expect,
|
expect,
|
||||||
Page,
|
Page,
|
||||||
Download,
|
Download,
|
||||||
TestInfo,
|
|
||||||
BrowserContext,
|
BrowserContext,
|
||||||
|
TestInfo,
|
||||||
_electron as electron,
|
_electron as electron,
|
||||||
Locator,
|
Locator,
|
||||||
|
test,
|
||||||
} from '@playwright/test'
|
} from '@playwright/test'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import os from 'os'
|
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import fsSync from 'fs'
|
import fsSync from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
@ -28,6 +28,8 @@ import * as TOML from '@iarna/toml'
|
|||||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||||
import { SETTINGS_FILE_NAME } from 'lib/constants'
|
import { SETTINGS_FILE_NAME } from 'lib/constants'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
import { isArray } from 'lib/utils'
|
||||||
|
import { reportRejection } from 'lib/trap'
|
||||||
|
|
||||||
type TestColor = [number, number, number]
|
type TestColor = [number, number, number]
|
||||||
export const TEST_COLORS = {
|
export const TEST_COLORS = {
|
||||||
@ -46,6 +48,9 @@ export const commonPoints = {
|
|||||||
num2: 14.44,
|
num2: 14.44,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const editorSelector = '[role="textbox"][data-language="kcl"]'
|
||||||
|
type PaneId = 'variables' | 'code' | 'files' | 'logs'
|
||||||
|
|
||||||
async function waitForPageLoadWithRetry(page: Page) {
|
async function waitForPageLoadWithRetry(page: Page) {
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
@ -75,11 +80,10 @@ async function waitForPageLoad(page: Page) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function removeCurrentCode(page: Page) {
|
async function removeCurrentCode(page: Page) {
|
||||||
const hotkey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
|
||||||
await page.locator('.cm-content').click()
|
await page.locator('.cm-content').click()
|
||||||
await page.keyboard.down(hotkey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('a')
|
await page.keyboard.press('a')
|
||||||
await page.keyboard.up(hotkey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
await page.keyboard.press('Backspace')
|
await page.keyboard.press('Backspace')
|
||||||
await expect(page.locator('.cm-content')).toHaveText('')
|
await expect(page.locator('.cm-content')).toHaveText('')
|
||||||
}
|
}
|
||||||
@ -207,7 +211,7 @@ export const wiggleMove = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const circleMove = async (
|
export const circleMove = async (
|
||||||
page: any,
|
page: Page,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
steps: number,
|
steps: number,
|
||||||
@ -313,13 +317,19 @@ export function normaliseKclNumbers(code: string, ignoreZero = true): string {
|
|||||||
return replaceNumbers(code)
|
return replaceNumbers(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUtils(page: Page) {
|
export async function getUtils(page: Page, test_?: typeof test) {
|
||||||
|
if (!test) {
|
||||||
|
console.warn(
|
||||||
|
'Some methods in getUtils requires test object as second argument'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Chrome devtools protocol session only works in Chromium
|
// Chrome devtools protocol session only works in Chromium
|
||||||
const browserType = page.context().browser()?.browserType().name()
|
const browserType = page.context().browser()?.browserType().name()
|
||||||
const cdpSession =
|
const cdpSession =
|
||||||
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
||||||
|
|
||||||
return {
|
const util = {
|
||||||
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||||
waitForPageLoad: () => waitForPageLoad(page),
|
waitForPageLoad: () => waitForPageLoad(page),
|
||||||
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
|
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
|
||||||
@ -432,46 +442,50 @@ export async function getUtils(page: Page) {
|
|||||||
}
|
}
|
||||||
return maxDiff
|
return maxDiff
|
||||||
},
|
},
|
||||||
doAndWaitForImageDiff: (fn: () => Promise<any>, diffCount = 200) =>
|
doAndWaitForImageDiff: (fn: () => Promise<unknown>, diffCount = 200) =>
|
||||||
new Promise(async (resolve) => {
|
new Promise<boolean>((resolve) => {
|
||||||
await page.screenshot({
|
;(async () => {
|
||||||
path: './e2e/playwright/temp1.png',
|
|
||||||
fullPage: true,
|
|
||||||
})
|
|
||||||
await fn()
|
|
||||||
const isImageDiff = async () => {
|
|
||||||
await page.screenshot({
|
await page.screenshot({
|
||||||
path: './e2e/playwright/temp2.png',
|
path: './e2e/playwright/temp1.png',
|
||||||
fullPage: true,
|
fullPage: true,
|
||||||
})
|
})
|
||||||
const screenshot1 = PNG.sync.read(
|
await fn()
|
||||||
await fsp.readFile('./e2e/playwright/temp1.png')
|
const isImageDiff = async () => {
|
||||||
)
|
await page.screenshot({
|
||||||
const screenshot2 = PNG.sync.read(
|
path: './e2e/playwright/temp2.png',
|
||||||
await fsp.readFile('./e2e/playwright/temp2.png')
|
fullPage: true,
|
||||||
)
|
})
|
||||||
const actualDiffCount = pixelMatch(
|
const screenshot1 = PNG.sync.read(
|
||||||
screenshot1.data,
|
await fsp.readFile('./e2e/playwright/temp1.png')
|
||||||
screenshot2.data,
|
)
|
||||||
null,
|
const screenshot2 = PNG.sync.read(
|
||||||
screenshot1.width,
|
await fsp.readFile('./e2e/playwright/temp2.png')
|
||||||
screenshot2.height
|
)
|
||||||
)
|
const actualDiffCount = pixelMatch(
|
||||||
return actualDiffCount > diffCount
|
screenshot1.data,
|
||||||
}
|
screenshot2.data,
|
||||||
|
null,
|
||||||
// run isImageDiff every 50ms until it returns true or 5 seconds have passed (100 times)
|
screenshot1.width,
|
||||||
let count = 0
|
screenshot2.height
|
||||||
const interval = setInterval(async () => {
|
)
|
||||||
count++
|
return actualDiffCount > diffCount
|
||||||
if (await isImageDiff()) {
|
|
||||||
clearInterval(interval)
|
|
||||||
resolve(true)
|
|
||||||
} else if (count > 100) {
|
|
||||||
clearInterval(interval)
|
|
||||||
resolve(false)
|
|
||||||
}
|
}
|
||||||
}, 50)
|
|
||||||
|
// run isImageDiff every 50ms until it returns true or 5 seconds have passed (100 times)
|
||||||
|
let count = 0
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
;(async () => {
|
||||||
|
count++
|
||||||
|
if (await isImageDiff()) {
|
||||||
|
clearInterval(interval)
|
||||||
|
resolve(true)
|
||||||
|
} else if (count > 100) {
|
||||||
|
clearInterval(interval)
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
})().catch(reportRejection)
|
||||||
|
}, 50)
|
||||||
|
})().catch(reportRejection)
|
||||||
}),
|
}),
|
||||||
emulateNetworkConditions: async (
|
emulateNetworkConditions: async (
|
||||||
networkOptions: Protocol.Network.emulateNetworkConditionsParameters
|
networkOptions: Protocol.Network.emulateNetworkConditionsParameters
|
||||||
@ -486,7 +500,127 @@ export async function getUtils(page: Page) {
|
|||||||
networkOptions
|
networkOptions
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toNormalizedCode: (text: string) => {
|
||||||
|
return text.replace(/\s+/g, '')
|
||||||
|
},
|
||||||
|
|
||||||
|
createAndSelectProject: async (hasText: string) => {
|
||||||
|
return test_?.step(
|
||||||
|
`Create and select project with text "${hasText}"`,
|
||||||
|
async () => {
|
||||||
|
await page.getByTestId('home-new-file').click()
|
||||||
|
const projectLinksPost = page.getByTestId('project-link')
|
||||||
|
await projectLinksPost.filter({ hasText }).click()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
editorTextMatches: async (code: string) => {
|
||||||
|
const editor = page.locator(editorSelector)
|
||||||
|
return expect(editor).toHaveText(code, { useInnerText: true })
|
||||||
|
},
|
||||||
|
|
||||||
|
pasteCodeInEditor: async (code: string) => {
|
||||||
|
return test?.step('Paste in KCL code', async () => {
|
||||||
|
const editor = page.locator(editorSelector)
|
||||||
|
await editor.fill(code)
|
||||||
|
await util.editorTextMatches(code)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
clickPane: async (paneId: PaneId) => {
|
||||||
|
return test?.step(`Open ${paneId} pane`, async () => {
|
||||||
|
await page.getByTestId(paneId + '-pane-button').click()
|
||||||
|
await expect(page.locator('#' + paneId + '-pane')).toBeVisible()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
createNewFile: async (name: string) => {
|
||||||
|
return test?.step(`Create a file named ${name}`, async () => {
|
||||||
|
await page.getByTestId('create-file-button').click()
|
||||||
|
await page.getByTestId('file-rename-field').fill(name)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
selectFile: async (name: string) => {
|
||||||
|
return test?.step(`Select ${name}`, async () => {
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: name })
|
||||||
|
.click()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
createNewFileAndSelect: async (name: string) => {
|
||||||
|
return test?.step(`Create a file named ${name}, select it`, async () => {
|
||||||
|
await openFilePanel(page)
|
||||||
|
await page.getByTestId('create-file-button').click()
|
||||||
|
await page.getByTestId('file-rename-field').fill(name)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
const newFile = page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: name })
|
||||||
|
|
||||||
|
await expect(newFile).toBeVisible()
|
||||||
|
await newFile.click()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
renameFile: async (fromName: string, toName: string) => {
|
||||||
|
return test?.step(`Rename ${fromName} to ${toName}`, async () => {
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: fromName })
|
||||||
|
.click({ button: 'right' })
|
||||||
|
await page.getByTestId('context-menu-rename').click()
|
||||||
|
await page.getByTestId('file-rename-field').fill(toName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: toName })
|
||||||
|
.click()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteFile: async (name: string) => {
|
||||||
|
return test?.step(`Delete ${name}`, async () => {
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: name })
|
||||||
|
.click({ button: 'right' })
|
||||||
|
await page.getByTestId('context-menu-delete').click()
|
||||||
|
await page.getByTestId('delete-confirmation').click()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Sorry I don't have time to fix this right now, but runs like
|
||||||
|
* the one linked below show me that setting the open panes in this manner is not reliable.
|
||||||
|
* You can either set `openPanes` as a part of the same initScript we run in setupElectron/setup,
|
||||||
|
* or you can imperatively open the panes with functions like {openKclCodePanel}
|
||||||
|
* (or we can make a general openPane function that takes a paneId).,
|
||||||
|
* but having a separate initScript does not seem to work reliably.
|
||||||
|
* @link https://github.com/KittyCAD/modeling-app/actions/runs/10731890169/job/29762700806?pr=3807#step:20:19553
|
||||||
|
*/
|
||||||
|
panesOpen: async (paneIds: PaneId[]) => {
|
||||||
|
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
||||||
|
await page.addInitScript(
|
||||||
|
({ PERSIST_MODELING_CONTEXT, paneIds }) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
PERSIST_MODELING_CONTEXT,
|
||||||
|
JSON.stringify({ openPanes: paneIds })
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ PERSIST_MODELING_CONTEXT, paneIds }
|
||||||
|
)
|
||||||
|
await page.reload()
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return util
|
||||||
}
|
}
|
||||||
|
|
||||||
type TemplateOptions = Array<number | Array<number>>
|
type TemplateOptions = Array<number | Array<number>>
|
||||||
@ -507,7 +641,7 @@ const _makeTemplate = (
|
|||||||
templateParts: TemplateStringsArray,
|
templateParts: TemplateStringsArray,
|
||||||
...options: TemplateOptions
|
...options: TemplateOptions
|
||||||
) => {
|
) => {
|
||||||
const length = Math.max(...options.map((a) => (Array.isArray(a) ? a[0] : 0)))
|
const length = Math.max(...options.map((a) => (isArray(a) ? a[0] : 0)))
|
||||||
let reExpTemplate = ''
|
let reExpTemplate = ''
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const currentStr = templateParts.map((str, index) => {
|
const currentStr = templateParts.map((str, index) => {
|
||||||
@ -515,7 +649,7 @@ const _makeTemplate = (
|
|||||||
return (
|
return (
|
||||||
escapeRegExp(str) +
|
escapeRegExp(str) +
|
||||||
String(
|
String(
|
||||||
Array.isArray(currentOptions)
|
isArray(currentOptions)
|
||||||
? currentOptions[i]
|
? currentOptions[i]
|
||||||
: typeof currentOptions === 'number'
|
: typeof currentOptions === 'number'
|
||||||
? currentOptions
|
? currentOptions
|
||||||
@ -669,11 +803,6 @@ export const doExport = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the appropriate modifier key for the platform.
|
|
||||||
*/
|
|
||||||
export const metaModifier = os.platform() === 'darwin' ? 'Meta' : 'Control'
|
|
||||||
|
|
||||||
export async function tearDown(page: Page, testInfo: TestInfo) {
|
export async function tearDown(page: Page, testInfo: TestInfo) {
|
||||||
if (testInfo.status === 'skipped') return
|
if (testInfo.status === 'skipped') return
|
||||||
if (testInfo.status === 'failed') return
|
if (testInfo.status === 'failed') return
|
||||||
@ -713,7 +842,6 @@ export async function setup(context: BrowserContext, page: Page) {
|
|||||||
localStorage.setItem('persistCode', ``)
|
localStorage.setItem('persistCode', ``)
|
||||||
localStorage.setItem(settingsKey, settings)
|
localStorage.setItem(settingsKey, settings)
|
||||||
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
|
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
|
||||||
console.log('TEST_SETTINGS.projects', settings)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
token: secrets.token,
|
token: secrets.token,
|
||||||
@ -770,6 +898,7 @@ export async function setup(context: BrowserContext, page: Page) {
|
|||||||
// kill animations, speeds up tests and reduced flakiness
|
// kill animations, speeds up tests and reduced flakiness
|
||||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||||
|
|
||||||
|
// Trigger a navigation, since loading file:// doesn't.
|
||||||
await page.reload()
|
await page.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -777,10 +906,12 @@ export async function setupElectron({
|
|||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn,
|
folderSetupFn,
|
||||||
cleanProjectDir = true,
|
cleanProjectDir = true,
|
||||||
|
appSettings,
|
||||||
}: {
|
}: {
|
||||||
testInfo: TestInfo
|
testInfo: TestInfo
|
||||||
folderSetupFn?: (projectDirName: string) => Promise<void>
|
folderSetupFn?: (projectDirName: string) => Promise<void>
|
||||||
cleanProjectDir?: boolean
|
cleanProjectDir?: boolean
|
||||||
|
appSettings?: Partial<SaveSettingsPayload>
|
||||||
}) {
|
}) {
|
||||||
// create or otherwise clear the folder
|
// create or otherwise clear the folder
|
||||||
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
||||||
@ -814,15 +945,19 @@ export async function setupElectron({
|
|||||||
|
|
||||||
if (cleanProjectDir) {
|
if (cleanProjectDir) {
|
||||||
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
|
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
|
||||||
const settingsOverrides = TOML.stringify({
|
const settingsOverrides = TOML.stringify(
|
||||||
...TEST_SETTINGS,
|
appSettings
|
||||||
settings: {
|
? { settings: appSettings }
|
||||||
app: {
|
: {
|
||||||
...TEST_SETTINGS.app,
|
...TEST_SETTINGS,
|
||||||
projectDirectory: projectDirName,
|
settings: {
|
||||||
},
|
app: {
|
||||||
},
|
...TEST_SETTINGS.app,
|
||||||
})
|
projectDirectory: projectDirName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
|
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -830,7 +965,7 @@ export async function setupElectron({
|
|||||||
|
|
||||||
await setup(context, page)
|
await setup(context, page)
|
||||||
|
|
||||||
return { electronApp, page }
|
return { electronApp, page, dir: projectDirName }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isOutOfViewInScrollContainer(
|
export async function isOutOfViewInScrollContainer(
|
||||||
@ -851,3 +986,33 @@ export async function isOutOfViewInScrollContainer(
|
|||||||
|
|
||||||
return isOutOfView
|
return isOutOfView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createProjectAndRenameIt({
|
||||||
|
name,
|
||||||
|
page,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
page: Page
|
||||||
|
}) {
|
||||||
|
await page.getByRole('button', { name: 'New project' }).click()
|
||||||
|
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||||
|
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.getByText(`project-000`)).toBeVisible()
|
||||||
|
await page.getByText(`project-000`).hover()
|
||||||
|
await page.getByText(`project-000`).focus()
|
||||||
|
|
||||||
|
await page.getByLabel('sketch').first().click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// type the name passed in
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.keyboard.type(name)
|
||||||
|
|
||||||
|
await page.getByLabel('checkmark').last().click()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function executorInputPath(fileName: string): string {
|
||||||
|
return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
|
||||||
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ test.afterEach(async ({ page }, testInfo) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Testing Camera Movement', () => {
|
test.describe('Testing Camera Movement', () => {
|
||||||
test('Can moving camera', async ({ page, context }) => {
|
test('Can move camera reliably', async ({ page, context }) => {
|
||||||
test.skip(process.platform === 'darwin', 'Can moving camera')
|
test.skip(process.platform === 'darwin', 'Can move camera reliably')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -102,6 +102,13 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
await bakeInRetries(async () => {
|
await bakeInRetries(async () => {
|
||||||
await page.mouse.move(700, 200)
|
await page.mouse.move(700, 200)
|
||||||
await page.mouse.down({ button: 'right' })
|
await page.mouse.down({ button: 'right' })
|
||||||
|
const appLogoBBox = await page.getByTestId('app-logo').boundingBox()
|
||||||
|
expect(appLogoBBox).not.toBeNull()
|
||||||
|
if (!appLogoBBox) throw new Error('app logo not found')
|
||||||
|
await page.mouse.move(
|
||||||
|
appLogoBBox.x + appLogoBBox.width / 2,
|
||||||
|
appLogoBBox.y + appLogoBBox.height / 2
|
||||||
|
)
|
||||||
await page.mouse.move(600, 303)
|
await page.mouse.move(600, 303)
|
||||||
await page.mouse.up({ button: 'right' })
|
await page.mouse.up({ button: 'right' })
|
||||||
}, [4, -10.5, -120])
|
}, [4, -10.5, -120])
|
||||||
@ -174,168 +181,166 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
}, [0, -85, -85])
|
}, [0, -85, -85])
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO fixme something is wrong with sketch here
|
test('Zoom should be consistent when exiting or entering sketches', async ({
|
||||||
test.fixme(
|
page,
|
||||||
'Zoom should be consistent when exiting or entering sketches',
|
}) => {
|
||||||
async ({ page }) => {
|
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
||||||
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
||||||
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
// than again for sketching
|
||||||
// than again for sketching
|
|
||||||
|
|
||||||
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// select a plane
|
||||||
|
await page.mouse.click(700, 325)
|
||||||
|
|
||||||
|
let code = `const sketch001 = startSketchOn('XY')`
|
||||||
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
|
// move the camera slightly
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.move(700, 300)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(800, 200)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
|
let y = 350,
|
||||||
|
x = 948
|
||||||
|
|
||||||
|
await u.canvasLocator.click({ position: { x: 783, y } })
|
||||||
|
code += `\n |> startProfileAt([8.12, -12.98], %)`
|
||||||
|
// await expect(u.codeLocator).toHaveText(code)
|
||||||
|
await u.canvasLocator.click({ position: { x, y } })
|
||||||
|
code += `\n |> line([11.18, 0], %)`
|
||||||
|
// await expect(u.codeLocator).toHaveText(code)
|
||||||
|
await u.canvasLocator.click({ position: { x, y: 275 } })
|
||||||
|
code += `\n |> line([0, 6.99], %)`
|
||||||
|
// await expect(u.codeLocator).toHaveText(code)
|
||||||
|
|
||||||
|
// click the line button
|
||||||
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
|
|
||||||
|
const hoverOverNothing = async () => {
|
||||||
|
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
|
||||||
|
await page.mouse.move(700, 325)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
|
||||||
// select a plane
|
|
||||||
await page.mouse.click(700, 325)
|
|
||||||
|
|
||||||
let code = `const sketch001 = startSketchOn('XY')`
|
|
||||||
await expect(u.codeLocator).toHaveText(code)
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
|
||||||
|
|
||||||
// move the camera slightly
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await page.mouse.move(700, 300)
|
|
||||||
await page.mouse.down({ button: 'right' })
|
|
||||||
await page.mouse.move(800, 200)
|
|
||||||
await page.mouse.up({ button: 'right' })
|
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
|
|
||||||
let y = 350,
|
|
||||||
x = 948
|
|
||||||
|
|
||||||
await u.canvasLocator.click({ position: { x: 783, y } })
|
|
||||||
code += `\n |> startProfileAt([8.12, -12.98], %)`
|
|
||||||
// await expect(u.codeLocator).toHaveText(code)
|
|
||||||
await u.canvasLocator.click({ position: { x, y } })
|
|
||||||
code += `\n |> line([11.18, 0], %)`
|
|
||||||
// await expect(u.codeLocator).toHaveText(code)
|
|
||||||
await u.canvasLocator.click({ position: { x, y: 275 } })
|
|
||||||
code += `\n |> line([0, 6.99], %)`
|
|
||||||
// await expect(u.codeLocator).toHaveText(code)
|
|
||||||
|
|
||||||
// click the line button
|
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
|
||||||
|
|
||||||
const hoverOverNothing = async () => {
|
|
||||||
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
|
|
||||||
await page.mouse.move(700, 325)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
||||||
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
// hover over horizontal line
|
|
||||||
await u.canvasLocator.hover({ position: { x: 800, y } })
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
// hover over vertical line
|
|
||||||
await u.canvasLocator.hover({ position: { x, y: 325 } })
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
// click exit sketch
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await page.waitForTimeout(400)
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
// hover over horizontal line
|
|
||||||
await page.mouse.move(858, y, { steps: 5 })
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
// hover over vertical line
|
|
||||||
await page.mouse.move(x, 325)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
// hover over vertical line
|
|
||||||
await page.mouse.move(857, y)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
// now click it
|
|
||||||
await page.mouse.click(857, y)
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
|
||||||
).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
|
||||||
|
|
||||||
await page.waitForTimeout(400)
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
x = 975
|
|
||||||
y = 468
|
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.mouse.move(x, 419, { steps: 5 })
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
await page.mouse.move(855, y)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
await page.mouse.move(x, 419)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
await page.mouse.move(855, y)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// hover over horizontal line
|
||||||
|
await u.canvasLocator.hover({ position: { x: 800, y } })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// hover over vertical line
|
||||||
|
await u.canvasLocator.hover({ position: { x, y: 325 } })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
// click exit sketch
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// hover over horizontal line
|
||||||
|
await page.mouse.move(858, y, { steps: 5 })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
// hover over vertical line
|
||||||
|
await page.mouse.move(x, 325)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
// hover over vertical line
|
||||||
|
await page.mouse.move(857, y)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
// now click it
|
||||||
|
await page.mouse.click(857, y)
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
|
||||||
|
x = 975
|
||||||
|
y = 468
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.move(x, 419, { steps: 5 })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
await page.mouse.move(855, y)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await page.mouse.move(x, 419)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
await page.mouse.move(855, y)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -31,8 +31,18 @@ test.describe('Testing selections', () => {
|
|||||||
|
|
||||||
const xAxisClick = () =>
|
const xAxisClick = () =>
|
||||||
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
|
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
|
||||||
|
const emptySpaceHover = () =>
|
||||||
|
test.step('Hover over empty space', async () => {
|
||||||
|
await page.mouse.move(700, 143, { steps: 5 })
|
||||||
|
await expect(page.locator('.hover-highlight')).not.toBeVisible()
|
||||||
|
})
|
||||||
const emptySpaceClick = () =>
|
const emptySpaceClick = () =>
|
||||||
page.mouse.click(700, 343).then(() => page.waitForTimeout(100))
|
test.step(`Click in empty space`, async () => {
|
||||||
|
await page.mouse.click(700, 143)
|
||||||
|
await expect(page.locator('.cm-line').last()).toHaveClass(
|
||||||
|
/cm-activeLine/
|
||||||
|
)
|
||||||
|
})
|
||||||
const topHorzSegmentClick = () =>
|
const topHorzSegmentClick = () =>
|
||||||
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
|
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
|
||||||
const bottomHorzSegmentClick = () =>
|
const bottomHorzSegmentClick = () =>
|
||||||
@ -171,7 +181,9 @@ test.describe('Testing selections', () => {
|
|||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
await selectionSequence()
|
await test.step(`Test hovering and selecting on fresh sketch`, async () => {
|
||||||
|
await selectionSequence()
|
||||||
|
})
|
||||||
|
|
||||||
// hovering in fresh sketch worked, lets try exiting and re-entering
|
// hovering in fresh sketch worked, lets try exiting and re-entering
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
@ -184,16 +196,15 @@ test.describe('Testing selections', () => {
|
|||||||
|
|
||||||
// select a line, this verifies that sketches in the scene can be selected outside of sketch mode
|
// select a line, this verifies that sketches in the scene can be selected outside of sketch mode
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
await page.waitForTimeout(100)
|
await emptySpaceHover()
|
||||||
|
|
||||||
// enter sketch again
|
// enter sketch again
|
||||||
await u.doAndWaitForCmd(
|
await u.doAndWaitForCmd(
|
||||||
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
||||||
'default_camera_get_settings'
|
'default_camera_get_settings'
|
||||||
)
|
)
|
||||||
await page.waitForTimeout(150)
|
|
||||||
|
|
||||||
await page.waitForTimeout(300) // wait for animation
|
await page.waitForTimeout(450) // wait for animation
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
@ -220,8 +231,9 @@ test.describe('Testing selections', () => {
|
|||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// hover again and check it works
|
await test.step(`Test hovering and selecting on edited sketch`, async () => {
|
||||||
await selectionSequence()
|
await selectionSequence()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Solids should be select and deletable', async ({ page }) => {
|
test('Solids should be select and deletable', async ({ page }) => {
|
||||||
@ -773,9 +785,9 @@ const extrude001 = extrude(50, sketch001)
|
|||||||
|
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
let noHoverColor: [number, number, number] = [82, 82, 82]
|
let noHoverColor: [number, number, number] = [92, 92, 92]
|
||||||
let hoverColor: [number, number, number] = [116, 116, 116]
|
let hoverColor: [number, number, number] = [127, 127, 127]
|
||||||
let selectColor: [number, number, number] = [144, 148, 97]
|
let selectColor: [number, number, number] = [155, 155, 105]
|
||||||
|
|
||||||
const extrudeWall = { x: 670, y: 275 }
|
const extrudeWall = { x: 670, y: 275 }
|
||||||
const extrudeText = `line([170.36, -121.61], %, $seg01)`
|
const extrudeText = `line([170.36, -121.61], %, $seg01)`
|
||||||
@ -787,7 +799,7 @@ const extrude001 = extrude(50, sketch001)
|
|||||||
|
|
||||||
await expect
|
await expect
|
||||||
.poll(() => u.getGreatestPixDiff(extrudeWall, noHoverColor))
|
.poll(() => u.getGreatestPixDiff(extrudeWall, noHoverColor))
|
||||||
.toBeLessThan(5)
|
.toBeLessThan(15)
|
||||||
await page.mouse.move(nothing.x, nothing.y)
|
await page.mouse.move(nothing.x, nothing.y)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.move(extrudeWall.x, extrudeWall.y)
|
await page.mouse.move(extrudeWall.x, extrudeWall.y)
|
||||||
@ -798,43 +810,43 @@ const extrude001 = extrude(50, sketch001)
|
|||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
await expect(
|
await expect(
|
||||||
await u.getGreatestPixDiff(extrudeWall, hoverColor)
|
await u.getGreatestPixDiff(extrudeWall, hoverColor)
|
||||||
).toBeLessThan(6)
|
).toBeLessThan(15)
|
||||||
await page.mouse.click(extrudeWall.x, extrudeWall.y)
|
await page.mouse.click(extrudeWall.x, extrudeWall.y)
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${extrudeText}`)
|
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${extrudeText}`)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
await expect(
|
await expect(
|
||||||
await u.getGreatestPixDiff(extrudeWall, selectColor)
|
await u.getGreatestPixDiff(extrudeWall, selectColor)
|
||||||
).toBeLessThan(6)
|
).toBeLessThan(15)
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
// check color stays there, i.e. not overridden (this was a bug previously)
|
// check color stays there, i.e. not overridden (this was a bug previously)
|
||||||
await expect(
|
await expect(
|
||||||
await u.getGreatestPixDiff(extrudeWall, selectColor)
|
await u.getGreatestPixDiff(extrudeWall, selectColor)
|
||||||
).toBeLessThan(6)
|
).toBeLessThan(15)
|
||||||
|
|
||||||
await page.mouse.move(nothing.x, nothing.y)
|
await page.mouse.move(nothing.x, nothing.y)
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
// because of shading, color is not exact everywhere on the face
|
// because of shading, color is not exact everywhere on the face
|
||||||
noHoverColor = [104, 104, 104]
|
noHoverColor = [115, 115, 115]
|
||||||
hoverColor = [134, 134, 134]
|
hoverColor = [145, 145, 145]
|
||||||
selectColor = [158, 162, 110]
|
selectColor = [168, 168, 120]
|
||||||
|
|
||||||
await expect(await u.getGreatestPixDiff(cap, noHoverColor)).toBeLessThan(6)
|
await expect(await u.getGreatestPixDiff(cap, noHoverColor)).toBeLessThan(15)
|
||||||
await page.mouse.move(cap.x, cap.y)
|
await page.mouse.move(cap.x, cap.y)
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toContainText(
|
await expect(page.getByTestId('hover-highlight').first()).toContainText(
|
||||||
removeAfterFirstParenthesis(capText)
|
removeAfterFirstParenthesis(capText)
|
||||||
)
|
)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
await expect(await u.getGreatestPixDiff(cap, hoverColor)).toBeLessThan(6)
|
await expect(await u.getGreatestPixDiff(cap, hoverColor)).toBeLessThan(15)
|
||||||
await page.mouse.click(cap.x, cap.y)
|
await page.mouse.click(cap.x, cap.y)
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${capText}`)
|
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${capText}`)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
await expect(await u.getGreatestPixDiff(cap, selectColor)).toBeLessThan(6)
|
await expect(await u.getGreatestPixDiff(cap, selectColor)).toBeLessThan(15)
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
// check color stays there, i.e. not overridden (this was a bug previously)
|
// check color stays there, i.e. not overridden (this was a bug previously)
|
||||||
await expect(await u.getGreatestPixDiff(cap, selectColor)).toBeLessThan(6)
|
await expect(await u.getGreatestPixDiff(cap, selectColor)).toBeLessThan(15)
|
||||||
})
|
})
|
||||||
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
|
||||||
page,
|
page,
|
||||||
|
|||||||
@ -1,8 +1,19 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { getUtils, setup, tearDown, setupElectron } from './test-utils'
|
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
import { join } from 'path'
|
||||||
import { TEST_SETTINGS_KEY, TEST_SETTINGS_CORRUPTED } from './storageStates'
|
import {
|
||||||
|
getUtils,
|
||||||
|
setup,
|
||||||
|
setupElectron,
|
||||||
|
tearDown,
|
||||||
|
executorInputPath,
|
||||||
|
} from './test-utils'
|
||||||
|
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
|
||||||
|
import {
|
||||||
|
TEST_SETTINGS_KEY,
|
||||||
|
TEST_SETTINGS_CORRUPTED,
|
||||||
|
TEST_SETTINGS,
|
||||||
|
} from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
@ -72,7 +83,7 @@ test.describe('Testing settings', () => {
|
|||||||
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
|
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
|
||||||
|
|
||||||
// Open the settings modal with the browser keyboard shortcut
|
// Open the settings modal with the browser keyboard shortcut
|
||||||
await page.keyboard.press('Meta+Shift+,')
|
await page.keyboard.press('ControlOrMeta+Shift+,')
|
||||||
|
|
||||||
await expect(headingLocator).toBeVisible()
|
await expect(headingLocator).toBeVisible()
|
||||||
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||||
@ -82,7 +93,7 @@ test.describe('Testing settings', () => {
|
|||||||
await test.step('Open settings with keyboard shortcut', async () => {
|
await test.step('Open settings with keyboard shortcut', async () => {
|
||||||
await page.getByTestId('settings-close-button').click()
|
await page.getByTestId('settings-close-button').click()
|
||||||
await page.locator('.cm-content').click()
|
await page.locator('.cm-content').click()
|
||||||
await page.keyboard.press('Meta+Shift+,')
|
await page.keyboard.press('ControlOrMeta+Shift+,')
|
||||||
await expect(headingLocator).toBeVisible()
|
await expect(headingLocator).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -115,31 +126,65 @@ test.describe('Testing settings', () => {
|
|||||||
).not.toBeChecked()
|
).not.toBeChecked()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Project and user settings can be reset', async ({ page }) => {
|
test('Keybindings display the correct hotkey for Command Palette', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await page
|
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
.waitFor({ state: 'visible' })
|
|
||||||
|
|
||||||
|
await test.step('Open keybindings settings', async () => {
|
||||||
|
// Open the settings modal with the browser keyboard shortcut
|
||||||
|
await page.keyboard.press('ControlOrMeta+Shift+,')
|
||||||
|
|
||||||
|
// Go to Keybindings tab.
|
||||||
|
const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' })
|
||||||
|
await keybindingsTab.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Go to the hotkey for Command Palette.
|
||||||
|
const commandPalette = page.getByText('Toggle Command Palette')
|
||||||
|
await commandPalette.scrollIntoViewIfNeeded()
|
||||||
|
|
||||||
|
// The heading is above it and should be in view now.
|
||||||
|
const commandPaletteHeading = page.getByRole('heading', {
|
||||||
|
name: 'Command Palette',
|
||||||
|
})
|
||||||
|
// The hotkey is in a kbd element next to the heading.
|
||||||
|
const hotkey = commandPaletteHeading.locator('+ div kbd')
|
||||||
|
const text = process.platform === 'darwin' ? 'Command+K' : 'Control+K'
|
||||||
|
await expect(hotkey).toHaveText(text)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Project and user settings can be reset', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await test.step(`Setup`, async () => {
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Selectors and constants
|
||||||
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
|
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
|
||||||
const userSettingsTab = page.getByRole('radio', { name: 'User' })
|
const userSettingsTab = page.getByRole('radio', { name: 'User' })
|
||||||
const resetButton = page.getByRole('button', {
|
const resetButton = (level: SettingsLevel) =>
|
||||||
name: 'Restore default settings',
|
page.getByRole('button', {
|
||||||
})
|
name: `Reset ${level}-level settings`,
|
||||||
|
})
|
||||||
const themeColorSetting = page.locator('#themeColor').getByRole('slider')
|
const themeColorSetting = page.locator('#themeColor').getByRole('slider')
|
||||||
const settingValues = {
|
const settingValues = {
|
||||||
default: '259',
|
default: '259',
|
||||||
user: '120',
|
user: '120',
|
||||||
project: '50',
|
project: '50',
|
||||||
}
|
}
|
||||||
|
const resetToast = (level: SettingsLevel) =>
|
||||||
|
page.getByText(`${level}-level settings were reset`)
|
||||||
|
|
||||||
// Open the settings modal with lower-right button
|
await test.step(`Open the settings modal`, async () => {
|
||||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('heading', { name: 'Settings', exact: true })
|
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
await test.step('Set up theme color', async () => {
|
await test.step('Set up theme color', async () => {
|
||||||
// Verify we're looking at the project-level settings,
|
// Verify we're looking at the project-level settings,
|
||||||
@ -158,37 +203,40 @@ test.describe('Testing settings', () => {
|
|||||||
|
|
||||||
await test.step('Reset project settings', async () => {
|
await test.step('Reset project settings', async () => {
|
||||||
// Click the reset settings button.
|
// Click the reset settings button.
|
||||||
await resetButton.click()
|
await resetButton('project').click()
|
||||||
|
|
||||||
await expect(page.getByText('Settings restored to default')).toBeVisible()
|
await expect(resetToast('project')).toBeVisible()
|
||||||
await expect(
|
await expect(resetToast('project')).not.toBeVisible()
|
||||||
page.getByText('Settings restored to default')
|
|
||||||
).not.toBeVisible()
|
|
||||||
|
|
||||||
// Verify it is now set to the inherited user value
|
// Verify it is now set to the inherited user value
|
||||||
await expect(themeColorSetting).toHaveValue(settingValues.default)
|
await expect(themeColorSetting).toHaveValue(settingValues.user)
|
||||||
|
|
||||||
// Check that the user setting also rolled back
|
await test.step(`Check that the user settings did not change`, async () => {
|
||||||
await userSettingsTab.click()
|
await userSettingsTab.click()
|
||||||
await expect(themeColorSetting).toHaveValue(settingValues.default)
|
await expect(themeColorSetting).toHaveValue(settingValues.user)
|
||||||
await projectSettingsTab.click()
|
})
|
||||||
|
|
||||||
// Set project-level value to 50 again to test the user-level reset
|
await test.step(`Set project-level again to test the user-level reset`, async () => {
|
||||||
await themeColorSetting.fill(settingValues.project)
|
await projectSettingsTab.click()
|
||||||
await userSettingsTab.click()
|
await themeColorSetting.fill(settingValues.project)
|
||||||
|
await userSettingsTab.click()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Reset user settings', async () => {
|
await test.step('Reset user settings', async () => {
|
||||||
// Change the setting and click the reset settings button.
|
// Click the reset settings button.
|
||||||
await themeColorSetting.fill(settingValues.user)
|
await resetButton('user').click()
|
||||||
await resetButton.click()
|
|
||||||
|
await expect(resetToast('user')).toBeVisible()
|
||||||
|
await expect(resetToast('user')).not.toBeVisible()
|
||||||
|
|
||||||
// Verify it is now set to the default value
|
// Verify it is now set to the default value
|
||||||
await expect(themeColorSetting).toHaveValue(settingValues.default)
|
await expect(themeColorSetting).toHaveValue(settingValues.default)
|
||||||
|
|
||||||
// Check that the project setting also changed
|
await test.step(`Check that the project settings did not change`, async () => {
|
||||||
await projectSettingsTab.click()
|
await projectSettingsTab.click()
|
||||||
await expect(themeColorSetting).toHaveValue(settingValues.default)
|
await expect(themeColorSetting).toHaveValue(settingValues.project)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -203,10 +251,11 @@ test.describe('Testing settings', () => {
|
|||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
const bracketDir = join(dir, 'bracket')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||||
`${dir}/bracket/main.kcl`
|
join(bracketDir, 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -250,7 +299,7 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Refresh the application and see project setting applied', async () => {
|
await test.step('Refresh the application and see project setting applied', async () => {
|
||||||
await page.reload()
|
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
||||||
await settingsCloseButton.click()
|
await settingsCloseButton.click()
|
||||||
@ -264,4 +313,410 @@ test.describe('Testing settings', () => {
|
|||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
`Load desktop app with no settings file`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
// This is what makes no settings file get created
|
||||||
|
cleanProjectDir: false,
|
||||||
|
testInfo,
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
// Selectors and constants
|
||||||
|
const errorHeading = page.getByRole('heading', {
|
||||||
|
name: 'An unextected error occurred',
|
||||||
|
})
|
||||||
|
const projectDirLink = page.getByText('Loaded from')
|
||||||
|
|
||||||
|
// If the app loads without exploding we're in the clear
|
||||||
|
await expect(errorHeading).not.toBeVisible()
|
||||||
|
await expect(projectDirLink).toBeVisible()
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
`Load desktop app with a settings file, but no project directory setting`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
appSettings: {
|
||||||
|
app: {
|
||||||
|
themeColor: '259',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
// Selectors and constants
|
||||||
|
const errorHeading = page.getByRole('heading', {
|
||||||
|
name: 'An unextected error occurred',
|
||||||
|
})
|
||||||
|
const projectDirLink = page.getByText('Loaded from')
|
||||||
|
|
||||||
|
// If the app loads without exploding we're in the clear
|
||||||
|
await expect(errorHeading).not.toBeVisible()
|
||||||
|
await expect(projectDirLink).toBeVisible()
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
`Closing settings modal should go back to the original file being viewed`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
const bracketDir = join(dir, 'project-000')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cube.kcl'),
|
||||||
|
join(bracketDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
join(bracketDir, '2.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8')
|
||||||
|
const kclCylinder = await fsp.readFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
const {
|
||||||
|
openKclCodePanel,
|
||||||
|
openFilePanel,
|
||||||
|
waitForPageLoad,
|
||||||
|
selectFile,
|
||||||
|
editorTextMatches,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await test.step('Precondition: Open to second project file', async () => {
|
||||||
|
await expect(page.getByTestId('home-section')).toBeVisible()
|
||||||
|
await page.getByText('project-000').click()
|
||||||
|
await waitForPageLoad()
|
||||||
|
await openKclCodePanel()
|
||||||
|
await openFilePanel()
|
||||||
|
await editorTextMatches(kclCube)
|
||||||
|
|
||||||
|
await selectFile('2.kcl')
|
||||||
|
await editorTextMatches(kclCylinder)
|
||||||
|
})
|
||||||
|
|
||||||
|
const settingsOpenButton = page.getByRole('link', {
|
||||||
|
name: 'settings Settings',
|
||||||
|
})
|
||||||
|
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||||
|
|
||||||
|
await test.step('Open and close settings', async () => {
|
||||||
|
await settingsOpenButton.click()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||||
|
).toBeVisible()
|
||||||
|
await settingsCloseButton.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Postcondition: Same file content is in editor as before settings opened', async () => {
|
||||||
|
await editorTextMatches(kclCylinder)
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test('Changing modeling default unit', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await test.step(`Test setup`, async () => {
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.waitFor({ state: 'visible' })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Selectors and constants
|
||||||
|
const userSettingsTab = page.getByRole('radio', { name: 'User' })
|
||||||
|
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
|
||||||
|
const defaultUnitSection = page.getByText(
|
||||||
|
'default unitRoll back default unitRoll back to match'
|
||||||
|
)
|
||||||
|
const defaultUnitRollbackButton = page.getByRole('button', {
|
||||||
|
name: 'Roll back default unit',
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Open the settings modal`, async () => {
|
||||||
|
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Settings', exact: true })
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Reset unit setting`, async () => {
|
||||||
|
await userSettingsTab.click()
|
||||||
|
await defaultUnitSection.hover()
|
||||||
|
await defaultUnitRollbackButton.click()
|
||||||
|
await projectSettingsTab.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Change modeling default unit within project tab', async () => {
|
||||||
|
const changeUnitOfMeasureInProjectTab = async (unitOfMeasure: string) => {
|
||||||
|
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
|
||||||
|
await page
|
||||||
|
.getByTestId('modeling-defaultUnit')
|
||||||
|
.selectOption(`${unitOfMeasure}`)
|
||||||
|
const toastMessage = page.getByText(
|
||||||
|
`Set default unit to "${unitOfMeasure}" for this project`
|
||||||
|
)
|
||||||
|
await expect(toastMessage).toBeVisible()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await changeUnitOfMeasureInProjectTab('in')
|
||||||
|
await changeUnitOfMeasureInProjectTab('ft')
|
||||||
|
await changeUnitOfMeasureInProjectTab('yd')
|
||||||
|
await changeUnitOfMeasureInProjectTab('mm')
|
||||||
|
await changeUnitOfMeasureInProjectTab('cm')
|
||||||
|
await changeUnitOfMeasureInProjectTab('m')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Go to the user tab
|
||||||
|
await userSettingsTab.click()
|
||||||
|
await test.step('Change modeling default unit within user tab', async () => {
|
||||||
|
const changeUnitOfMeasureInUserTab = async (unitOfMeasure: string) => {
|
||||||
|
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
|
||||||
|
await page
|
||||||
|
.getByTestId('modeling-defaultUnit')
|
||||||
|
.selectOption(`${unitOfMeasure}`)
|
||||||
|
const toastMessage = page.getByText(
|
||||||
|
`Set default unit to "${unitOfMeasure}" as a user default`
|
||||||
|
)
|
||||||
|
await expect(toastMessage).toBeVisible()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await changeUnitOfMeasureInUserTab('in')
|
||||||
|
await changeUnitOfMeasureInUserTab('ft')
|
||||||
|
await changeUnitOfMeasureInUserTab('yd')
|
||||||
|
await changeUnitOfMeasureInUserTab('mm')
|
||||||
|
await changeUnitOfMeasureInUserTab('cm')
|
||||||
|
await changeUnitOfMeasureInUserTab('m')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Close settings
|
||||||
|
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||||
|
await settingsCloseButton.click()
|
||||||
|
|
||||||
|
await test.step('Change modeling default unit within command bar', async () => {
|
||||||
|
const commands = page.getByRole('button', { name: 'Commands' })
|
||||||
|
const changeUnitOfMeasureInCommandBar = async (unitOfMeasure: string) => {
|
||||||
|
// Open command bar
|
||||||
|
await commands.click()
|
||||||
|
const settingsModelingDefaultUnitCommand = page.getByText(
|
||||||
|
'Settings · modeling · default unit'
|
||||||
|
)
|
||||||
|
await settingsModelingDefaultUnitCommand.click()
|
||||||
|
|
||||||
|
const commandOption = page.getByRole('option', {
|
||||||
|
name: unitOfMeasure,
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
await commandOption.click()
|
||||||
|
|
||||||
|
const toastMessage = page.getByText(
|
||||||
|
`Set default unit to "${unitOfMeasure}" for this project`
|
||||||
|
)
|
||||||
|
await expect(toastMessage).toBeVisible()
|
||||||
|
}
|
||||||
|
await changeUnitOfMeasureInCommandBar('in')
|
||||||
|
await changeUnitOfMeasureInCommandBar('ft')
|
||||||
|
await changeUnitOfMeasureInCommandBar('yd')
|
||||||
|
await changeUnitOfMeasureInCommandBar('mm')
|
||||||
|
await changeUnitOfMeasureInCommandBar('cm')
|
||||||
|
await changeUnitOfMeasureInCommandBar('m')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Change modeling default unit within gizmo', async () => {
|
||||||
|
const changeUnitOfMeasureInGizmo = async (
|
||||||
|
unitOfMeasure: string,
|
||||||
|
copy: string
|
||||||
|
) => {
|
||||||
|
const gizmo = page.getByRole('button', {
|
||||||
|
name: 'Current units are: ',
|
||||||
|
})
|
||||||
|
await gizmo.click()
|
||||||
|
const button = page.getByRole('button', {
|
||||||
|
name: copy,
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
await button.click()
|
||||||
|
const toastMessage = page.getByText(
|
||||||
|
`Set default unit to "${unitOfMeasure}" for this project`
|
||||||
|
)
|
||||||
|
await expect(toastMessage).toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
await changeUnitOfMeasureInGizmo('in', 'Inches')
|
||||||
|
await changeUnitOfMeasureInGizmo('ft', 'Feet')
|
||||||
|
await changeUnitOfMeasureInGizmo('yd', 'Yards')
|
||||||
|
await changeUnitOfMeasureInGizmo('mm', 'Millimeters')
|
||||||
|
await changeUnitOfMeasureInGizmo('cm', 'Centimeters')
|
||||||
|
await changeUnitOfMeasureInGizmo('m', 'Meters')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Changing theme in sketch mode', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([5, 0], %)
|
||||||
|
|> line([0, 5], %)
|
||||||
|
|> line([-5, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(5, sketch001)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
// Selectors and constants
|
||||||
|
const editSketchButton = page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
const lineToolButton = page.getByTestId('line')
|
||||||
|
const segmentOverlays = page.getByTestId('segment-overlay')
|
||||||
|
const sketchOriginLocation = { x: 600, y: 250 }
|
||||||
|
const darkThemeSegmentColor: [number, number, number] = [215, 215, 215]
|
||||||
|
const lightThemeSegmentColor: [number, number, number] = [90, 90, 90]
|
||||||
|
|
||||||
|
await test.step(`Get into sketch mode`, async () => {
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
await expect(editSketchButton).toBeVisible()
|
||||||
|
await editSketchButton.click()
|
||||||
|
|
||||||
|
// We use the line tool as a proxy for sketch mode
|
||||||
|
await expect(lineToolButton).toBeVisible()
|
||||||
|
await expect(segmentOverlays).toHaveCount(4)
|
||||||
|
// but we allow more time to pass for animating to the sketch
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Check the sketch line color before`, async () => {
|
||||||
|
await expect
|
||||||
|
.poll(() =>
|
||||||
|
u.getGreatestPixDiff(sketchOriginLocation, darkThemeSegmentColor)
|
||||||
|
)
|
||||||
|
.toBeLessThan(15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Change theme to light using command palette`, async () => {
|
||||||
|
await page.keyboard.press('ControlOrMeta+K')
|
||||||
|
await page.getByRole('option', { name: 'theme' }).click()
|
||||||
|
await page.getByRole('option', { name: 'light' }).click()
|
||||||
|
await expect(page.getByText('theme to "light"')).toBeVisible()
|
||||||
|
|
||||||
|
// Make sure we haven't left sketch mode
|
||||||
|
await expect(lineToolButton).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Check the sketch line color after`, async () => {
|
||||||
|
await expect
|
||||||
|
.poll(() =>
|
||||||
|
u.getGreatestPixDiff(sketchOriginLocation, lightThemeSegmentColor)
|
||||||
|
)
|
||||||
|
.toBeLessThan(15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Override beforeEach test setup
|
||||||
|
// with debug panel open
|
||||||
|
// but "show debug panel" set to false
|
||||||
|
await page.addInitScript(
|
||||||
|
async ({ settingsKey, settings }) => {
|
||||||
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistModelingContext',
|
||||||
|
'{"openPanes":["debug"]}'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
|
settings: TOML.stringify({
|
||||||
|
settings: {
|
||||||
|
...TEST_SETTINGS,
|
||||||
|
modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const resizeHandle = page.locator('.sidebar-resize-handles > div.block')
|
||||||
|
const debugPaneButton = page.getByTestId('debug-pane-button')
|
||||||
|
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
||||||
|
const debugPaneOption = page.getByRole('option', {
|
||||||
|
name: 'Settings · modeling · show debug panel',
|
||||||
|
})
|
||||||
|
|
||||||
|
async function setShowDebugPanelTo(value: 'On' | 'Off') {
|
||||||
|
await commandsButton.click()
|
||||||
|
await debugPaneOption.click()
|
||||||
|
await page.getByRole('option', { name: value }).click()
|
||||||
|
await expect(
|
||||||
|
page.getByText(
|
||||||
|
`Set show debug panel to "${value === 'On'}" for this project`
|
||||||
|
)
|
||||||
|
).toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step(`Initial load with corrupted settings`, async () => {
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
// Check that the debug panel is not visible
|
||||||
|
await expect(debugPaneButton).not.toBeVisible()
|
||||||
|
// Check the pane resize handle wrapper is not visible
|
||||||
|
await expect(resizeHandle).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Open code pane to verify we see the resize handles`, async () => {
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await expect(resizeHandle).toBeVisible()
|
||||||
|
await u.closeKclCodePanel()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Turn on debug panel, open it`, async () => {
|
||||||
|
await setShowDebugPanelTo('On')
|
||||||
|
await expect(debugPaneButton).toBeVisible()
|
||||||
|
// We want the logic to clear the phantom panel, so we shouldn't see
|
||||||
|
// the real panel (and therefore the resize handle) yet
|
||||||
|
await expect(resizeHandle).not.toBeVisible()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await expect(resizeHandle).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Turn off debug panel setting with it open`, async () => {
|
||||||
|
await setShowDebugPanelTo('Off')
|
||||||
|
await expect(debugPaneButton).not.toBeVisible()
|
||||||
|
await expect(resizeHandle).not.toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page } from '@playwright/test'
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
import { getUtils, setup, tearDown, setupElectron } from './test-utils'
|
||||||
|
import { join } from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
await setup(context, page)
|
await setup(context, page)
|
||||||
@ -9,8 +11,6 @@ test.afterEach(async ({ page }, testInfo) => {
|
|||||||
await tearDown(page, testInfo)
|
await tearDown(page, testInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
|
||||||
|
|
||||||
test.describe('Text-to-CAD tests', () => {
|
test.describe('Text-to-CAD tests', () => {
|
||||||
test('basic lego happy case', async ({ page }) => {
|
test('basic lego happy case', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -298,9 +298,9 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(page.locator('textarea')).toContainText(badPrompt)
|
await expect(page.locator('textarea')).toContainText(badPrompt)
|
||||||
|
|
||||||
// Select all and start a new prompt.
|
// Select all and start a new prompt.
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyA')
|
await page.keyboard.press('KeyA')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
await page.keyboard.type('a 2x4 lego')
|
await page.keyboard.type('a 2x4 lego')
|
||||||
|
|
||||||
// Submit the new prompt.
|
// Submit the new prompt.
|
||||||
@ -520,9 +520,9 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||||
|
|
||||||
// Paste the code.
|
// Paste the code.
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyV')
|
await page.keyboard.press('KeyV')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
// Expect the code to be pasted.
|
||||||
await expect(page.locator('.cm-content')).toContainText(`2x8`)
|
await expect(page.locator('.cm-content')).toContainText(`2x8`)
|
||||||
@ -534,7 +534,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
// Ensure the final toast remains.
|
// Ensure the final toast remains.
|
||||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||||
await expect(page.getByText(`a 2x8 lego`)).not.toBeVisible()
|
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
|
||||||
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
|
||||||
|
|
||||||
// Ensure you can copy the code for the final model.
|
// Ensure you can copy the code for the final model.
|
||||||
@ -549,13 +549,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||||
|
|
||||||
// Paste the code.
|
// Paste the code.
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyA')
|
await page.keyboard.press('KeyA')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
await page.keyboard.press('Backspace')
|
await page.keyboard.press('Backspace')
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyV')
|
await page.keyboard.press('KeyV')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
// Expect the code to be pasted.
|
||||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||||
@ -636,9 +636,9 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||||
|
|
||||||
// Paste the code.
|
// Paste the code.
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('KeyV')
|
await page.keyboard.press('KeyV')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
// Expect the code to be pasted.
|
||||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||||
@ -685,3 +685,75 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Text-to-CAD functionality',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const projectName = 'project-000'
|
||||||
|
const prompt = 'lego 2x4'
|
||||||
|
const textToCadFileName = 'lego-2x4.kcl'
|
||||||
|
|
||||||
|
const { electronApp, page, dir } = await setupElectron({ testInfo })
|
||||||
|
const fileExists = () =>
|
||||||
|
fs.existsSync(join(dir, projectName, textToCadFileName))
|
||||||
|
|
||||||
|
const {
|
||||||
|
createAndSelectProject,
|
||||||
|
openFilePanel,
|
||||||
|
openKclCodePanel,
|
||||||
|
waitForPageLoad,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
// Locators
|
||||||
|
const projectMenuButton = page.getByRole('button', { name: projectName })
|
||||||
|
const textToCadFileButton = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: textToCadFileName }),
|
||||||
|
})
|
||||||
|
const textToCadComment = page.getByText(
|
||||||
|
`// Generated by Text-to-CAD: ${prompt}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create and navigate to the project
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
|
||||||
|
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
|
||||||
|
await waitForPageLoad()
|
||||||
|
await openFilePanel()
|
||||||
|
await openKclCodePanel()
|
||||||
|
|
||||||
|
await test.step(`Test file creation`, async () => {
|
||||||
|
await sendPromptFromCommandBar(page, prompt)
|
||||||
|
// File is considered created if it shows up in the Project Files pane
|
||||||
|
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
|
||||||
|
expect(fileExists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Test file navigation`, async () => {
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
await textToCadFileButton.click()
|
||||||
|
// File can be navigated and loaded assuming a specific KCL comment is loaded into the KCL code pane
|
||||||
|
await expect(textToCadComment).toBeVisible({ timeout: 20_000 })
|
||||||
|
await expect(projectMenuButton).toContainText(textToCadFileName)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Test file deletion on rejection`, async () => {
|
||||||
|
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
||||||
|
// A file is created and can be navigated to while this prompt is still opened
|
||||||
|
// Click the "Reject" button within the prompt and it will delete the file.
|
||||||
|
await rejectButton.click()
|
||||||
|
|
||||||
|
const submittingToastMessage = page.getByText(
|
||||||
|
`Successfully deleted file "lego-2x4.kcl"`
|
||||||
|
)
|
||||||
|
await expect(submittingToastMessage).toBeVisible()
|
||||||
|
expect(fileExists()).toBeFalsy()
|
||||||
|
// Confirm we've navigated back to the main.kcl file after deletion
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@ -1,13 +1,6 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
import {
|
import { doExport, getUtils, makeTemplate, setup, tearDown } from './test-utils'
|
||||||
doExport,
|
|
||||||
getUtils,
|
|
||||||
makeTemplate,
|
|
||||||
metaModifier,
|
|
||||||
setup,
|
|
||||||
tearDown,
|
|
||||||
} from './test-utils'
|
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
await setup(context, page)
|
await setup(context, page)
|
||||||
@ -17,8 +10,6 @@ test.afterEach(async ({ page }, testInfo) => {
|
|||||||
await tearDown(page, testInfo)
|
await tearDown(page, testInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
|
||||||
|
|
||||||
test('Units menu', async ({ page }) => {
|
test('Units menu', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
@ -157,7 +148,7 @@ test('Paste should not work unless an input is focused', async ({
|
|||||||
|
|
||||||
// Paste without the code pane focused
|
// Paste without the code pane focused
|
||||||
await codeEditorText.blur()
|
await codeEditorText.blur()
|
||||||
await page.keyboard.press(`${metaModifier}+KeyV`)
|
await page.keyboard.press('ControlOrMeta+KeyV')
|
||||||
|
|
||||||
// Show that the paste didn't work but typing did
|
// Show that the paste didn't work but typing did
|
||||||
await expect(codeEditorText).not.toContainText(pasteContent)
|
await expect(codeEditorText).not.toContainText(pasteContent)
|
||||||
@ -166,7 +157,7 @@ test('Paste should not work unless an input is focused', async ({
|
|||||||
// Paste with the code editor focused
|
// Paste with the code editor focused
|
||||||
// Following this guidance: https://github.com/microsoft/playwright/issues/8114
|
// Following this guidance: https://github.com/microsoft/playwright/issues/8114
|
||||||
await codeEditorText.focus()
|
await codeEditorText.focus()
|
||||||
await page.keyboard.press(`${metaModifier}+KeyV`)
|
await page.keyboard.press('ControlOrMeta+KeyV')
|
||||||
await expect(
|
await expect(
|
||||||
await page.evaluate(
|
await page.evaluate(
|
||||||
() => document.querySelector('.cm-content')?.textContent
|
() => document.querySelector('.cm-content')?.textContent
|
||||||
@ -380,9 +371,9 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
|||||||
await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => {
|
await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => {
|
||||||
// Since there's code now, we have to get to the end of the line
|
// Since there's code now, we have to get to the end of the line
|
||||||
await page.locator('.cm-line').last().click()
|
await page.locator('.cm-line').last().click()
|
||||||
await page.keyboard.down(CtrlKey)
|
await page.keyboard.down('ControlOrMeta')
|
||||||
await page.keyboard.press('ArrowRight')
|
await page.keyboard.press('ArrowRight')
|
||||||
await page.keyboard.up(CtrlKey)
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.keyboard.type('//')
|
await page.keyboard.type('//')
|
||||||
|
|||||||
83
electron-builder.yml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
appId: dev.zoo.modeling-app
|
||||||
|
|
||||||
|
directories:
|
||||||
|
output: out
|
||||||
|
buildResources: assets
|
||||||
|
|
||||||
|
files:
|
||||||
|
- .vite/**
|
||||||
|
|
||||||
|
mac:
|
||||||
|
category: public.app-category.developer-tools
|
||||||
|
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
|
||||||
|
target:
|
||||||
|
- target: dmg
|
||||||
|
arch:
|
||||||
|
- x64
|
||||||
|
- arm64
|
||||||
|
- target: zip
|
||||||
|
arch:
|
||||||
|
- x64
|
||||||
|
- arm64
|
||||||
|
notarize:
|
||||||
|
teamId: 92H8YB3B95
|
||||||
|
fileAssociations:
|
||||||
|
- ext: kcl
|
||||||
|
name: kcl
|
||||||
|
mimeType: text/vnd.zoo.kcl
|
||||||
|
description: Zoo KCL File
|
||||||
|
role: Editor
|
||||||
|
rank: Owner
|
||||||
|
|
||||||
|
win:
|
||||||
|
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
|
||||||
|
target:
|
||||||
|
- target: nsis
|
||||||
|
arch:
|
||||||
|
- x64
|
||||||
|
- arm64
|
||||||
|
- target: msi
|
||||||
|
arch:
|
||||||
|
- x64
|
||||||
|
- arm64
|
||||||
|
signingHashAlgorithms:
|
||||||
|
- sha256
|
||||||
|
sign: "./sign-win.js"
|
||||||
|
publisherName: "KittyCAD Inc" # needs to be exactly like on Digicert
|
||||||
|
icon: "assets/icon.ico"
|
||||||
|
fileAssociations:
|
||||||
|
- ext: kcl
|
||||||
|
name: kcl
|
||||||
|
mimeType: text/vnd.zoo.kcl
|
||||||
|
description: Zoo KCL File
|
||||||
|
role: Editor
|
||||||
|
|
||||||
|
msi:
|
||||||
|
oneClick: false
|
||||||
|
perMachine: true
|
||||||
|
|
||||||
|
nsis:
|
||||||
|
oneClick: false
|
||||||
|
perMachine: true
|
||||||
|
allowElevation: true
|
||||||
|
installerIcon: "assets/icon.ico"
|
||||||
|
include: "./installer.nsh"
|
||||||
|
|
||||||
|
linux:
|
||||||
|
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
|
||||||
|
target:
|
||||||
|
- target: appImage
|
||||||
|
arch:
|
||||||
|
- x64
|
||||||
|
- arm64
|
||||||
|
fileAssociations:
|
||||||
|
- ext: kcl
|
||||||
|
name: kcl
|
||||||
|
mimeType: text/vnd.zoo.kcl
|
||||||
|
description: Zoo KCL File
|
||||||
|
role: Editor
|
||||||
|
|
||||||
|
publish:
|
||||||
|
- provider: generic
|
||||||
|
url: https://dl.zoo.dev/releases/modeling-app
|
||||||
|
channel: latest
|
||||||
@ -4,10 +4,17 @@ import { MakerZIP } from '@electron-forge/maker-zip'
|
|||||||
import { MakerDeb } from '@electron-forge/maker-deb'
|
import { MakerDeb } from '@electron-forge/maker-deb'
|
||||||
import { MakerRpm } from '@electron-forge/maker-rpm'
|
import { MakerRpm } from '@electron-forge/maker-rpm'
|
||||||
import { VitePlugin } from '@electron-forge/plugin-vite'
|
import { VitePlugin } from '@electron-forge/plugin-vite'
|
||||||
|
import { MakerWix, MakerWixConfig } from '@electron-forge/maker-wix'
|
||||||
import { FusesPlugin } from '@electron-forge/plugin-fuses'
|
import { FusesPlugin } from '@electron-forge/plugin-fuses'
|
||||||
import { FuseV1Options, FuseVersion } from '@electron/fuses'
|
import { FuseV1Options, FuseVersion } from '@electron/fuses'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
|
interface ExtendedMakerWixConfig extends MakerWixConfig {
|
||||||
|
// see https://github.com/electron/forge/issues/3673
|
||||||
|
// this is an undocumented property of electron-wix-msi
|
||||||
|
associateExtensions?: string
|
||||||
|
}
|
||||||
|
|
||||||
const rootDir = process.cwd()
|
const rootDir = process.cwd()
|
||||||
|
|
||||||
const config: ForgeConfig = {
|
const config: ForgeConfig = {
|
||||||
@ -23,12 +30,23 @@ const config: ForgeConfig = {
|
|||||||
undefined,
|
undefined,
|
||||||
executableName: 'zoo-modeling-app',
|
executableName: 'zoo-modeling-app',
|
||||||
icon: path.resolve(rootDir, 'assets', 'icon'),
|
icon: path.resolve(rootDir, 'assets', 'icon'),
|
||||||
|
protocols: [
|
||||||
|
{
|
||||||
|
name: 'Zoo Studio',
|
||||||
|
schemes: ['zoo-studio'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extendInfo: 'Info.plist', // Information for file associations.
|
||||||
},
|
},
|
||||||
rebuildConfig: {},
|
rebuildConfig: {},
|
||||||
makers: [
|
makers: [
|
||||||
new MakerSquirrel({
|
new MakerSquirrel({
|
||||||
setupIcon: path.resolve(rootDir, 'assets', 'icon.ico'),
|
setupIcon: path.resolve(rootDir, 'assets', 'icon.ico'),
|
||||||
}),
|
}),
|
||||||
|
new MakerWix({
|
||||||
|
icon: path.resolve(rootDir, 'assets', 'icon.ico'),
|
||||||
|
associateExtensions: 'kcl',
|
||||||
|
} as ExtendedMakerWixConfig),
|
||||||
new MakerZIP({}, ['darwin']),
|
new MakerZIP({}, ['darwin']),
|
||||||
new MakerRpm({
|
new MakerRpm({
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<link rel="stylesheet" href="./inter/inter.css" />
|
||||||
<link rel="stylesheet" href="https://use.typekit.net/zzv8rvm.css" />
|
<link rel="stylesheet" href="https://use.typekit.net/zzv8rvm.css" />
|
||||||
<script
|
<script
|
||||||
defer
|
defer
|
||||||
|
|||||||
8
installer.nsh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
!macro preInit
|
||||||
|
SetRegView 64
|
||||||
|
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
|
||||||
|
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
|
||||||
|
SetRegView 32
|
||||||
|
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
|
||||||
|
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App"
|
||||||
|
!macroend
|
||||||
1
interface.d.ts
vendored
@ -30,7 +30,6 @@ export interface IElectronAPI {
|
|||||||
join: typeof path.join
|
join: typeof path.join
|
||||||
sep: typeof path.sep
|
sep: typeof path.sep
|
||||||
rename: (prev: string, next: string) => typeof fs.rename
|
rename: (prev: string, next: string) => typeof fs.rename
|
||||||
setBaseUrl: (value: string) => void
|
|
||||||
packageJson: {
|
packageJson: {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|||||||
48
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zoo-modeling-app",
|
"name": "zoo-modeling-app",
|
||||||
"version": "0.24.12",
|
"version": "0.25.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"author": {
|
"author": {
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^2.0.0",
|
"@kittycad/lib": "^2.0.1",
|
||||||
"@lezer/highlight": "^1.2.1",
|
"@lezer/highlight": "^1.2.1",
|
||||||
"@lezer/lr": "^1.4.1",
|
"@lezer/lr": "^1.4.1",
|
||||||
"@react-hook/resize-observer": "^2.0.1",
|
"@react-hook/resize-observer": "^2.0.1",
|
||||||
@ -34,22 +34,24 @@
|
|||||||
"@ts-stack/markdown": "^1.5.0",
|
"@ts-stack/markdown": "^1.5.0",
|
||||||
"@tweenjs/tween.js": "^23.1.1",
|
"@tweenjs/tween.js": "^23.1.1",
|
||||||
"@xstate/inspect": "^0.8.0",
|
"@xstate/inspect": "^0.8.0",
|
||||||
"@xstate/react": "^3.2.2",
|
"@xstate/react": "^4.1.1",
|
||||||
"bonjour-service": "^1.2.1",
|
"bonjour-service": "^1.2.1",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
|
"electron-updater": "^6.3.0",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"html2canvas-pro": "^1.5.8",
|
"html2canvas-pro": "^1.5.8",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"json-rpc-2.0": "^1.6.0",
|
"json-rpc-2.0": "^1.6.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
"minimist": "^1.2.8",
|
||||||
"openid-client": "^5.6.5",
|
"openid-client": "^5.6.5",
|
||||||
"re-resizable": "^6.9.11",
|
"re-resizable": "^6.9.11",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-hotkeys-hook": "^4.5.0",
|
"react-hotkeys-hook": "^4.5.1",
|
||||||
"react-json-view": "^1.21.3",
|
"react-json-view": "^1.21.3",
|
||||||
"react-modal": "^3.16.1",
|
"react-modal": "^3.16.1",
|
||||||
"react-modal-promise": "^1.0.2",
|
"react-modal-promise": "^1.0.2",
|
||||||
@ -62,7 +64,7 @@
|
|||||||
"vscode-languageserver-protocol": "^3.17.5",
|
"vscode-languageserver-protocol": "^3.17.5",
|
||||||
"vscode-uri": "^3.0.8",
|
"vscode-uri": "^3.0.8",
|
||||||
"web-vitals": "^3.5.2",
|
"web-vitals": "^3.5.2",
|
||||||
"xstate": "^4.38.2"
|
"xstate": "^5.17.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
@ -82,14 +84,13 @@
|
|||||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||||
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
"build:wasm": "(cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
|
||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
||||||
"lint": "eslint --fix src e2e",
|
"lint": "eslint --fix src e2e packages/codemirror-lsp-client",
|
||||||
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
|
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||||
"postinstall": "yarn xstate:typegen",
|
"postinstall": "yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
||||||
"make:dev": "make dev",
|
"make:dev": "make dev",
|
||||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||||
@ -97,7 +98,9 @@
|
|||||||
"tron:package": "electron-forge package",
|
"tron:package": "electron-forge package",
|
||||||
"tron:make": "electron-forge make",
|
"tron:make": "electron-forge make",
|
||||||
"tron:publish": "electron-forge publish",
|
"tron:publish": "electron-forge publish",
|
||||||
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron"
|
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron",
|
||||||
|
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
|
||||||
|
"tronb:package": "electron-builder --config electron-builder.yml"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
@ -119,16 +122,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@babel/preset-env": "^7.24.3",
|
"@babel/preset-env": "^7.25.4",
|
||||||
"@electron-forge/cli": "^7.4.0",
|
"@electron-forge/cli": "^7.4.0",
|
||||||
"@electron-forge/maker-deb": "^7.4.0",
|
"@electron-forge/maker-deb": "^7.4.0",
|
||||||
"@electron-forge/maker-rpm": "^7.4.0",
|
"@electron-forge/maker-rpm": "^7.4.0",
|
||||||
"@electron-forge/maker-squirrel": "^7.4.0",
|
"@electron-forge/maker-squirrel": "^7.4.0",
|
||||||
|
"@electron-forge/maker-wix": "^7.4.0",
|
||||||
"@electron-forge/maker-zip": "^7.4.0",
|
"@electron-forge/maker-zip": "^7.4.0",
|
||||||
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
||||||
"@electron-forge/plugin-fuses": "^7.4.0",
|
"@electron-forge/plugin-fuses": "^7.4.0",
|
||||||
"@electron-forge/plugin-vite": "^7.4.0",
|
"@electron-forge/plugin-vite": "^7.4.0",
|
||||||
"@electron/fuses": "^1.8.0",
|
"@electron/fuses": "^1.8.0",
|
||||||
|
"@electron/rebuild": "^3.6.0",
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@lezer/generator": "^1.7.1",
|
"@lezer/generator": "^1.7.1",
|
||||||
"@playwright/test": "^1.46.1",
|
"@playwright/test": "^1.46.1",
|
||||||
@ -137,8 +142,9 @@
|
|||||||
"@types/d3-force": "^3.0.10",
|
"@types/d3-force": "^3.0.10",
|
||||||
"@types/electron": "^1.6.10",
|
"@types/electron": "^1.6.10",
|
||||||
"@types/isomorphic-fetch": "^0.0.39",
|
"@types/isomorphic-fetch": "^0.0.39",
|
||||||
|
"@types/minimist": "^1.2.5",
|
||||||
"@types/mocha": "^10.0.6",
|
"@types/mocha": "^10.0.6",
|
||||||
"@types/node": "^22.4.2",
|
"@types/node": "^22.5.0",
|
||||||
"@types/pixelmatch": "^5.2.6",
|
"@types/pixelmatch": "^5.2.6",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
@ -147,7 +153,6 @@
|
|||||||
"@types/three": "^0.163.0",
|
"@types/three": "^0.163.0",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@types/wait-on": "^5.3.4",
|
|
||||||
"@types/wicg-file-system-access": "^2023.10.5",
|
"@types/wicg-file-system-access": "^2023.10.5",
|
||||||
"@types/ws": "^8.5.10",
|
"@types/ws": "^8.5.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
@ -158,31 +163,32 @@
|
|||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"d3-force": "^3.0.0",
|
"d3-force": "^3.0.0",
|
||||||
"electron": "^32.0.1",
|
"electron": "^32.0.1",
|
||||||
|
"electron-builder": "^24.13.3",
|
||||||
|
"electron-notarize": "^1.2.2",
|
||||||
"eslint": "^8.0.1",
|
"eslint": "^8.0.1",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
"eslint-plugin-import": "^2.25.0",
|
"eslint-plugin-import": "^2.30.0",
|
||||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||||
"happy-dom": "^14.3.10",
|
"happy-dom": "^14.3.10",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.1.5",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"pixelmatch": "^5.3.0",
|
"pixelmatch": "^5.3.0",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.43",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"setimmediate": "^1.0.5",
|
"setimmediate": "^1.0.5",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.4.2",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^1.6.0",
|
"vitest": "^1.6.0",
|
||||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||||
"wait-on": "^7.2.0",
|
|
||||||
"wasm-pack": "^0.13.0",
|
"wasm-pack": "^0.13.0",
|
||||||
"ws": "^8.17.0",
|
"ws": "^8.17.0",
|
||||||
"yarn": "^1.22.22"
|
"yarn": "^1.22.22"
|
||||||
|
|||||||
@ -72,6 +72,7 @@ export class LanguageServerClient {
|
|||||||
async initialize() {
|
async initialize() {
|
||||||
// Start the client in the background.
|
// Start the client in the background.
|
||||||
this.client.setNotifyFn(this.processNotifications.bind(this))
|
this.client.setNotifyFn(this.processNotifications.bind(this))
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.client.start()
|
this.client.start()
|
||||||
|
|
||||||
this.ready = true
|
this.ready = true
|
||||||
@ -195,6 +196,9 @@ export class LanguageServerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private processNotifications(notification: LSP.NotificationMessage) {
|
private processNotifications(notification: LSP.NotificationMessage) {
|
||||||
for (const plugin of this.plugins) plugin.processNotification(notification)
|
for (const plugin of this.plugins) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
plugin.processNotification(notification)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export default function lspFormatExt(
|
|||||||
run: (view: EditorView) => {
|
run: (view: EditorView) => {
|
||||||
let value = view.plugin(plugin)
|
let value = view.plugin(plugin)
|
||||||
if (!value) return false
|
if (!value) return false
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
value.requestFormatting()
|
value.requestFormatting()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|||||||
@ -117,6 +117,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
|
|
||||||
this.processLspNotification = options.processLspNotification
|
this.processLspNotification = options.processLspNotification
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.initialize({
|
this.initialize({
|
||||||
documentText: this.getDocText(),
|
documentText: this.getDocText(),
|
||||||
})
|
})
|
||||||
@ -149,6 +150,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initialize({ documentText }: { documentText: string }) {
|
async initialize({ documentText }: { documentText: string }) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
if (this.client.initializePromise) {
|
if (this.client.initializePromise) {
|
||||||
await this.client.initializePromise
|
await this.client.initializePromise
|
||||||
}
|
}
|
||||||
@ -162,7 +164,9 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.requestSemanticTokens()
|
this.requestSemanticTokens()
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.updateFoldingRanges()
|
this.updateFoldingRanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +229,9 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
contentChanges: [{ text: this.view.state.doc.toString() }],
|
contentChanges: [{ text: this.view.state.doc.toString() }],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.requestSemanticTokens()
|
this.requestSemanticTokens()
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.updateFoldingRanges()
|
this.updateFoldingRanges()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@ -526,7 +532,9 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
processDiagnostics(params: PublishDiagnosticsParams) {
|
processDiagnostics(params: PublishDiagnosticsParams) {
|
||||||
if (params.uri !== this.getDocUri()) return
|
if (params.uri !== this.getDocUri()) return
|
||||||
|
|
||||||
const diagnostics = params.diagnostics
|
// Commented to avoid the lint. See TODO below.
|
||||||
|
// const diagnostics =
|
||||||
|
params.diagnostics
|
||||||
.map(({ range, message, severity }) => ({
|
.map(({ range, message, severity }) => ({
|
||||||
from: posToOffset(this.view.state.doc, range.start)!,
|
from: posToOffset(this.view.state.doc, range.start)!,
|
||||||
to: posToOffset(this.view.state.doc, range.end)!,
|
to: posToOffset(this.view.state.doc, range.end)!,
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test'
|
|
||||||
import dotenv from 'dotenv'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
|
||||||
*/
|
|
||||||
export default defineConfig({
|
|
||||||
timeout: 120_000, // override the default 30s timeout
|
|
||||||
testDir: './e2e/playwright',
|
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
/* Do not retry */
|
|
||||||
retries: process.env.CI ? 0 : 0,
|
|
||||||
/* Different amount of parallelism on CI and local. */
|
|
||||||
workers: process.env.CI ? 1 : 4,
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: [
|
|
||||||
[process.env.CI ? 'dot' : 'list'],
|
|
||||||
['json', { outputFile: './test-results/report.json' }],
|
|
||||||
['html'],
|
|
||||||
],
|
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
||||||
use: {
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: 'retain-on-failure',
|
|
||||||
actionTimeout: 15000,
|
|
||||||
screenshot: 'only-on-failure',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@ -17,6 +17,7 @@ export default defineConfig({
|
|||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: [
|
reporter: [
|
||||||
['dot'],
|
['dot'],
|
||||||
|
['list'],
|
||||||
['json', { outputFile: './test-results/report.json' }],
|
['json', { outputFile: './test-results/report.json' }],
|
||||||
['html'],
|
['html'],
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from '@playwright/test'
|
import { defineConfig, devices } from '@playwright/test'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
@ -30,4 +30,24 @@ export default defineConfig({
|
|||||||
actionTimeout: 15_000,
|
actionTimeout: 15_000,
|
||||||
screenshot: 'only-on-failure',
|
screenshot: 'only-on-failure',
|
||||||
},
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'Google Chrome',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Chrome'],
|
||||||
|
channel: 'chrome',
|
||||||
|
contextOptions: {
|
||||||
|
/* Chromium is the only one with these permission types */
|
||||||
|
permissions: ['clipboard-write', 'clipboard-read'],
|
||||||
|
},
|
||||||
|
launchOptions: {
|
||||||
|
...(process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
|
||||||
|
? {
|
||||||
|
executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
}, // or 'chrome-beta'
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
BIN
public/inter/InterVariable-Italic.woff2
Normal file
BIN
public/inter/InterVariable.woff2
Normal file
14
public/inter/inter.css
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: Inter;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("InterVariable.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Inter;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("InterVariable-Italic.woff2") format("woff2");
|
||||||
|
}
|
||||||
BIN
public/wheel-loop-dark.mp4
Normal file
BIN
public/wheel-loop.mp4
Normal file
38
sign-win.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// From https://github.com/OpenBuilds/OpenBuilds-CONTROL/blob/4800540ffaa517925fc2cff26670809efa341ffe/signWin.js
|
||||||
|
const { execSync } = require('node:child_process')
|
||||||
|
|
||||||
|
exports.default = async (configuration) => {
|
||||||
|
if (!process.env.SM_API_KEY) {
|
||||||
|
console.error(
|
||||||
|
'Signing using signWin.js script: failed: SM_API_KEY ENV VAR NOT FOUND'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.WINDOWS_CERTIFICATE_THUMBPRINT) {
|
||||||
|
console.error(
|
||||||
|
'Signing using signWin.js script: failed: FINGERPRINT ENV VAR NOT FOUND'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configuration.path) {
|
||||||
|
throw new Error(
|
||||||
|
`Signing using signWin.js script: failed: TARGET PATH NOT FOUND`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
execSync(
|
||||||
|
`smctl sign --fingerprint="${
|
||||||
|
process.env.WINDOWS_CERTIFICATE_THUMBPRINT
|
||||||
|
}" --input "${String(configuration.path)}"`,
|
||||||
|
{
|
||||||
|
stdio: 'inherit',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
console.log('Signing using signWin.js script: successful')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Signing using signWin.js script: failed:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/App.tsx
@ -1,12 +1,8 @@
|
|||||||
import { MouseEventHandler, useEffect, useMemo, useRef } from 'react'
|
import { useEffect, useMemo, useRef } from 'react'
|
||||||
import { uuidv4 } from 'lib/utils'
|
|
||||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||||
import { Stream } from './components/Stream'
|
import { Stream } from './components/Stream'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
|
||||||
import { throttle } from './lib/utils'
|
|
||||||
import { AppHeader } from './components/AppHeader'
|
import { AppHeader } from './components/AppHeader'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { getNormalisedCoordinates } from './lib/utils'
|
|
||||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
@ -14,7 +10,6 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||||
import { codeManager, engineCommandManager } from 'lib/singletons'
|
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { useLspContext } from 'components/LspProvider'
|
import { useLspContext } from 'components/LspProvider'
|
||||||
@ -44,7 +39,6 @@ export function App() {
|
|||||||
}, [projectName, projectPath])
|
}, [projectName, projectPath])
|
||||||
|
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
const { context, state } = useModelingContext()
|
|
||||||
|
|
||||||
const { auth, settings } = useSettingsAuthContext()
|
const { auth, settings } = useSettingsAuthContext()
|
||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
@ -73,52 +67,14 @@ export function App() {
|
|||||||
(p) => p === onboardingStatus.current
|
(p) => p === onboardingStatus.current
|
||||||
)
|
)
|
||||||
? 'opacity-20'
|
? 'opacity-20'
|
||||||
: context.store?.didDragInStream
|
|
||||||
? 'opacity-40'
|
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
useEngineConnectionSubscriptions()
|
useEngineConnectionSubscriptions()
|
||||||
|
|
||||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
|
||||||
engineCommandManager.sendSceneCommand(message)
|
|
||||||
}, 1000 / 15)
|
|
||||||
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
|
||||||
if (state.matches('Sketch')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { x, y } = getNormalisedCoordinates({
|
|
||||||
clientX: e.clientX,
|
|
||||||
clientY: e.clientY,
|
|
||||||
el: e.currentTarget,
|
|
||||||
...context.store?.streamDimensions,
|
|
||||||
})
|
|
||||||
|
|
||||||
const newCmdId = uuidv4()
|
|
||||||
if (state.matches('idle.showPlanes')) return
|
|
||||||
if (context.store?.buttonDownInStream !== undefined) return
|
|
||||||
debounceSocketSend({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'highlight_set_entity',
|
|
||||||
selected_at_window: { x, y },
|
|
||||||
},
|
|
||||||
cmd_id: newCmdId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="relative h-full flex flex-col" ref={ref}>
|
||||||
className="relative h-full flex flex-col"
|
|
||||||
onMouseMove={handleMouseMove}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
<AppHeader
|
<AppHeader
|
||||||
className={
|
className={'transition-opacity transition-duration-75 ' + paneOpacity}
|
||||||
'transition-opacity transition-duration-75 ' +
|
|
||||||
paneOpacity +
|
|
||||||
(context.store?.buttonDownInStream ? ' pointer-events-none' : '')
|
|
||||||
}
|
|
||||||
project={{ project, file }}
|
project={{ project, file }}
|
||||||
enableMenu={true}
|
enableMenu={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -33,7 +33,6 @@ import SettingsAuthProvider from 'components/SettingsAuthProvider'
|
|||||||
import LspProvider from 'components/LspProvider'
|
import LspProvider from 'components/LspProvider'
|
||||||
import { KclContextProvider } from 'lang/KclProvider'
|
import { KclContextProvider } from 'lang/KclProvider'
|
||||||
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||||
import { getState, setState } from 'lib/desktop'
|
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { codeManager, engineCommandManager } from 'lib/singletons'
|
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
@ -42,6 +41,7 @@ import toast from 'react-hot-toast'
|
|||||||
import { coreDump } from 'lang/wasm'
|
import { coreDump } from 'lang/wasm'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { AppStateProvider } from 'AppState'
|
import { AppStateProvider } from 'AppState'
|
||||||
|
import { reportRejection } from 'lib/trap'
|
||||||
|
|
||||||
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
||||||
|
|
||||||
@ -70,23 +70,6 @@ const router = createRouter([
|
|||||||
path: PATHS.INDEX,
|
path: PATHS.INDEX,
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
const onDesktop = isDesktop()
|
const onDesktop = isDesktop()
|
||||||
if (onDesktop) {
|
|
||||||
const appState = await getState()
|
|
||||||
|
|
||||||
if (appState) {
|
|
||||||
// Reset the state.
|
|
||||||
// We do this so that we load the initial state from the cli but everything
|
|
||||||
// else we can ignore.
|
|
||||||
await setState(undefined)
|
|
||||||
// Redirect to the file if we have a file path.
|
|
||||||
if (appState.current_file) {
|
|
||||||
return redirect(
|
|
||||||
PATHS.FILE + '/' + encodeURIComponent(appState.current_file)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return onDesktop
|
return onDesktop
|
||||||
? redirect(PATHS.HOME)
|
? redirect(PATHS.HOME)
|
||||||
: redirect(PATHS.FILE + '/%2F' + BROWSER_PROJECT_NAME)
|
: redirect(PATHS.FILE + '/%2F' + BROWSER_PROJECT_NAME)
|
||||||
@ -190,22 +173,24 @@ function CoreDump() {
|
|||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
useHotkeyWrapper(['meta + shift + .'], () => {
|
useHotkeyWrapper(['mod + shift + .'], () => {
|
||||||
toast.promise(
|
toast
|
||||||
coreDump(coreDumpManager, true),
|
.promise(
|
||||||
{
|
coreDump(coreDumpManager, true),
|
||||||
loading: 'Starting core dump...',
|
{
|
||||||
success: 'Core dump completed successfully',
|
loading: 'Starting core dump...',
|
||||||
error: 'Error while exporting core dump',
|
success: 'Core dump completed successfully',
|
||||||
},
|
error: 'Error while exporting core dump',
|
||||||
{
|
|
||||||
success: {
|
|
||||||
// Note: this extended duration is especially important for Playwright e2e testing
|
|
||||||
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
|
||||||
duration: 6000,
|
|
||||||
},
|
},
|
||||||
}
|
{
|
||||||
)
|
success: {
|
||||||
|
// Note: this extended duration is especially important for Playwright e2e testing
|
||||||
|
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
||||||
|
duration: 6000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch(reportRejection)
|
||||||
})
|
})
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||