Compare commits

..

9 Commits

Author SHA1 Message Date
ae068b7ed6 closer
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 17:02:37 -07:00
5bdc899831 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:25:08 -07:00
e0e6acf231 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:08:11 -07:00
668bc863df updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:06:56 -07:00
59ce602d6e updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:06:44 -07:00
8ce819b28f updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:58:08 -07:00
ed5e276377 working
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:47:38 -07:00
8549bd9486 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:41:03 -07:00
56f9078812 init
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:33:31 -07:00
461 changed files with 8596 additions and 17084 deletions

View File

@ -3,4 +3,3 @@ VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000
VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"

View File

@ -1,33 +0,0 @@
name: Build and Store WASM
on:
push:
branches:
- main
jobs:
build-and-upload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache wasm
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: build wasm
run: yarn build:wasm
# Upload the WASM bundle as an artifact
- uses: actions/upload-artifact@v3
with:
name: wasm-bundle
path: src/wasm-lib/pkg

View File

@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
dir: ['src/wasm-lib', 'src-tauri']
dir: ['src/wasm-lib']
steps:
- uses: actions/checkout@v4
- name: Install latest rust
@ -31,22 +31,9 @@ jobs:
- name: install dependencies
if: matrix.dir == 'src-tauri'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y \
libgtk-3-dev \
libayatana-appindicator3-dev \
webkit2gtk-driver \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
libwebkit2gtk-4.1-dev \
at-spi2-core \
xvfb
yarn install
yarn build:wasm
yarn build:local
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1

View File

@ -1,57 +0,0 @@
on:
push:
branches:
- main
paths:
- 'src-tauri/**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-test-tauri.yml
pull_request:
paths:
- 'src-tauri/**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-test-tauri.yml
workflow_dispatch:
permissions: read-all
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo test of tauri
jobs:
cargotest:
name: cargo test
runs-on: ubuntu-latest-8-cores
strategy:
matrix:
dir: ['src-tauri']
steps:
- uses: actions/checkout@v4
- name: Install latest rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: install dependencies
if: matrix.dir == 'src-tauri'
run: |
sudo apt-get update
sudo apt-get install -y \
libgtk-3-dev \
libayatana-appindicator3-dev \
webkit2gtk-driver \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
libwebkit2gtk-4.1-dev \
at-spi2-core \
xvfb
- name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1
- name: cargo test
shell: bash
run: |-
cd "${{ matrix.dir }}"
cargo test --all

View File

@ -13,7 +13,7 @@ on:
# Will checkout the last commit from the default branch (main as of 2023-10-04)
env:
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && contains(github.event.pull_request.title, 'Cut release v') }}
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -130,9 +130,7 @@ jobs:
matrix:
os: [macos-14, ubuntu-latest, windows-latest]
env:
# Specific Apple Universal target for macos
TAURI_ARGS_MACOS: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
# Only build executable on linux (no appimage or deb)
TAURI_ARGS_UBUNTU: ${{ matrix.os == 'ubuntu-latest' && '--bundles' || '' }}
steps:
- uses: actions/checkout@v4
@ -149,16 +147,16 @@ jobs:
- name: Install ubuntu system dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y \
libgtk-3-dev \
libayatana-appindicator3-dev \
webkit2gtk-driver \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev \
libwebkit2gtk-4.1-dev \
at-spi2-core \
run: >
sudo apt-get update &&
sudo apt-get install -y
libgtk-3-dev
libayatana-appindicator3-dev
webkit2gtk-driver
libsoup-3.0-dev
libjavascriptcoregtk-4.1-dev
libwebkit2gtk-4.1-dev
at-spi2-core
xvfb
- name: Sync node version and setup cache
@ -239,96 +237,6 @@ jobs:
includeDebug: true
args: "${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
- name: Build for Mac TestFlight (nightly)
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
shell: bash
run: |
unset APPLE_SIGNING_IDENTITY
unset APPLE_CERTIFICATE
sign_app="3rd Party Mac Developer Application: KittyCAD Inc (${APPLE_TEAM_ID})"
sign_install="3rd Party Mac Developer Installer: KittyCAD Inc (${APPLE_TEAM_ID})"
profile="src-tauri/entitlements/Mac_App_Distribution.provisionprofile"
mkdir -p src-tauri/entitlements
echo -n "${APPLE_STORE_PROVISIONING_PROFILE}" | base64 --decode -o "${profile}"
echo -n "${APPLE_STORE_DISTRIBUTION_CERT}" | base64 --decode -o "dist.cer"
echo -n "${APPLE_STORE_INSTALLER_CERT}" | base64 --decode -o "installer.cer"
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD="password"
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import "dist.cer" -P "$APPLE_STORE_P12_PASSWORD" -k $KEYCHAIN_PATH -f pkcs12 -t cert -A
security import "installer.cer" -P "$APPLE_STORE_P12_PASSWORD" -k $KEYCHAIN_PATH -f pkcs12 -t cert -A
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
target="universal-apple-darwin"
# Turn off the default target
# We don't want to install the updater for the apple store build
sed -i.bu "s/default =/# default =/" src-tauri/Cargo.toml
rm src-tauri/Cargo.toml.bu
git diff src-tauri/Cargo.toml
yarn tauri build --target "${target}" --verbose --config src-tauri/tauri.app-store.conf.json
app_path="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app"
build_name="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.pkg"
cp_dir="src-tauri/target/${target}/release/bundle/macos/Zoo Modeling App.app/Contents/embedded.provisionprofile"
entitlements="src-tauri/entitlements/app-store.entitlements"
cp "${profile}" "${cp_dir}"
codesign --deep --force -s "${sign_app}" --entitlements "${entitlements}" "${app_path}"
productbuild --component "${app_path}" /Applications/ --sign "${sign_install}" "${build_name}"
# Undo the changes to the Cargo.toml
git checkout src-tauri/Cargo.toml
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_STORE_PROVISIONING_PROFILE: ${{ secrets.APPLE_STORE_PROVISIONING_PROFILE }}
APPLE_STORE_DISTRIBUTION_CERT: ${{ secrets.APPLE_STORE_DISTRIBUTION_CERT }}
APPLE_STORE_INSTALLER_CERT: ${{ secrets.APPLE_STORE_INSTALLER_CERT }}
APPLE_STORE_P12_PASSWORD: ${{ secrets.APPLE_STORE_P12_PASSWORD }}
- name: 'Upload to Mac TestFlight (nightly)'
uses: apple-actions/upload-testflight-build@v1
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
with:
app-path: 'src-tauri/target/universal-apple-darwin/release/bundle/macos/Zoo Modeling App.pkg'
issuer-id: ${{ secrets.APPLE_STORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPLE_STORE_API_KEY_ID }}
api-private-key: ${{ secrets.APPLE_STORE_API_PRIVATE_KEY }}
app-type: osx
- name: Clean up after Mac TestFlight (nightly)
if: ${{ github.event_name == 'schedule' && matrix.os == 'macos-14' }}
shell: bash
run: |
git status
# remove our target builds because we want to make sure the later build
# includes the updater, and that anything we changed with the target
# does not persist
rm -rf src-tauri/target
# Lets get rid of the info.plist for the normal mac builds since its
# being sketchy.
rm src-tauri/Info.plist
# We do this after the apple store because the apple store build is
# specific and we want to overwrite it with the this new build after and
# not upload the apple store build to the public bucket
- name: Build the app (release) and sign
uses: tauri-apps/tauri-action@v0
if: ${{ env.BUILD_RELEASE == 'true' }}
@ -353,10 +261,11 @@ jobs:
with:
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
# TODO: re-enable linux e2e tests when possible
- name: Run e2e tests (linux only)
if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
if: false
run: |
cargo install tauri-driver --force
cargo install tauri-driver
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
export VITE_KC_API_BASE_URL
xvfb-run yarn test:e2e:tauri
@ -440,7 +349,7 @@ jobs:
cat last_download.json
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.3'
uses: 'google-github-actions/auth@v2.1.2'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'

View File

@ -1,37 +0,0 @@
name: Create Release
on:
push:
branches:
- main
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
if: contains(github.event.head_commit.message, 'Cut release v')
steps:
- uses: actions/github-script@v7
name: Read Cut release PR info and create release
with:
script: |
const { owner, repo } = context.repo
const pulls = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: context.sha,
})
const { title, body } = pulls.data[0]
const version = title.split('Cut release ')[1]
const result = await github.rest.repos.createRelease({
owner,
repo,
body,
tag_name: version,
name: version,
draft: true,
})
console.log(result)

View File

@ -12,31 +12,11 @@ concurrency:
permissions:
contents: write
pull-requests: write
actions: read
jobs:
check-rust-changes:
runs-on: ubuntu-latest
outputs:
rust-changed: ${{ steps.filter.outputs.rust }}
steps:
- uses: actions/checkout@v4
- id: filter
name: Check for Rust changes
uses: dorny/paths-filter@v3
with:
filters: |
rust:
- 'src/wasm-lib/**'
playwright-ubuntu:
timeout-minutes: 60
runs-on: ubuntu-latest-8-cores
needs: check-rust-changes
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@ -48,38 +28,13 @@ jobs:
run: yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Download Wasm Cache
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v3
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: src/wasm-lib/pkg
- name: copy wasm blob
if: needs.check-rust-changes.outputs.rust-changed == 'false'
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
continue-on-error: true
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Wasm (because rust diff)
if: needs.check-rust-changes.outputs.rust-changed == 'true'
- name: Cache wasm
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: OR Cache Wasm (because wasm cache failed)
if: steps.download-wasm.outcome == 'failure'
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Build Wasm (because rust diff)
if: needs.check-rust-changes.outputs.rust-changed == 'true'
run: yarn build:wasm
- name: OR Build Wasm (because wasm cache failed)
if: steps.download-wasm.outcome == 'failure'
- name: build wasm
run: yarn build:wasm
- name: build web
run: yarn build:local
@ -134,7 +89,6 @@ jobs:
playwright-macos:
timeout-minutes: 60
runs-on: macos-14
needs: check-rust-changes
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@ -145,38 +99,13 @@ jobs:
run: yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Download Wasm Cache
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v3
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: src/wasm-lib/pkg
- name: copy wasm blob
if: needs.check-rust-changes.outputs.rust-changed == 'false'
run: cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
continue-on-error: true
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Wasm (because rust diff)
if: needs.check-rust-changes.outputs.rust-changed == 'true'
- name: Cache wasm
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: OR Cache Wasm (because wasm cache failed)
if: steps.download-wasm.outcome == 'failure'
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: Build Wasm (because rust diff)
if: needs.check-rust-changes.outputs.rust-changed == 'true'
run: yarn build:wasm
- name: OR Build Wasm (because wasm cache failed)
if: steps.download-wasm.outcome == 'failure'
- name: build wasm
run: yarn build:wasm
- name: build web
run: yarn build:local

1
.gitignore vendored
View File

@ -54,4 +54,3 @@ src/**/*.typegen.ts
src-tauri/gen
src/wasm-lib/grackle/stdlib_cube_partial.json
Mac_App_Distribution.provisionprofile

2
.nvmrc
View File

@ -1 +1 @@
v21.7.1
v20.5.0

View File

@ -59,10 +59,6 @@ followed by:
```
yarn build:wasm-dev
```
or if you have the gh cli installed
```
./get-latest-wasm-bundle.sh # this will download the latest main wasm bundle
```
That will build the WASM binary and put in the `public` dir (though gitignored)
@ -72,13 +68,7 @@ finally, to run the web app only, run:
yarn start
```
If you're not an KittyCAD employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens ofcourse, then navigate to localhost:3000 again. Note that navigating to localhost:3000/signin removes your token so you will need to set the token again.
### Development environment variables
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `dev.zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
### Developing in Chrome
## Developing in Chrome
Chrome is in the process of rolling out a new default which
[blocks Third-Party Cookies](https://developer.chrome.com/en/docs/privacy-sandbox/third-party-cookie-phase-out/).

View File

@ -1,3 +1,3 @@
module.exports = {
presets: ['@babel/preset-env'],
presets: ["@babel/preset-env"],
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -31,6 +31,7 @@ layout: manual
* [`fillet`](kcl/fillet)
* [`floor`](kcl/floor)
* [`getEdge`](kcl/getEdge)
* [`getExtrudeWallTransform`](kcl/getExtrudeWallTransform)
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
* [`getOppositeEdge`](kcl/getOppositeEdge)
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { makeTemplate, getUtils } from './test-utils'
import { getUtils } from './test-utils'
import waitOn from 'wait-on'
import { roundOff } from 'lib/utils'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
@ -8,11 +8,9 @@ import {
TEST_SETTINGS,
TEST_SETTINGS_KEY,
TEST_SETTINGS_CORRUPTED,
TEST_SETTINGS_ONBOARDING_EXPORT,
TEST_SETTINGS_ONBOARDING_START,
TEST_SETTINGS_ONBOARDING,
} from './storageStates'
import * as TOML from '@iarna/toml'
import { Coords2d } from 'lang/std/sketch'
/*
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
@ -106,7 +104,6 @@ test('Basic sketch', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
@ -131,7 +128,6 @@ test('Basic sketch', async ({ page }) => {
// selected two lines therefore there should be two cursors
await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.getByRole('button', { name: 'Constrain' }).click()
await page.getByRole('button', { name: 'Equal Length' }).click()
await expect(page.locator('.cm-content'))
@ -265,88 +261,6 @@ test('Can moving camera', async ({ page, context }) => {
}, [1, -94, -94])
})
test('if you click the format button it formats your code', async ({
page,
}) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1000, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
// check no error to begin with
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await page.click('.cm-content')
await page.keyboard.type(`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)`)
await page.click('#code-pane button:first-child')
await page.click('button:has-text("Format code")')
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)`)
})
test('if you use the format keyboard binding it formats your code', async ({
page,
}) => {
const u = getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)`
)
})
await page.setViewportSize({ width: 1000, height: 500 })
const lspStartPromise = page.waitForEvent('console', async (message) => {
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
// but that doesn't seem to make it to the console for macos/safari :(
if (message.text().includes('start kcl lsp')) {
await new Promise((resolve) => setTimeout(resolve, 200))
return true
}
return false
})
await page.goto('/')
await u.waitForAuthSkipAppStart()
await lspStartPromise
// check no error to begin with
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// focus the editor
await page.click('.cm-line')
// Hit alt+shift+f to format the code
await page.keyboard.press('Alt+Shift+KeyF')
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> line([-20, 0], %)
|> close(%)`)
})
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
const u = getUtils(page)
await page.setViewportSize({ width: 1000, height: 500 })
@ -363,7 +277,7 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
const bottomAng = 25
*/
await page.click('.cm-content')
await page.keyboard.type('$ error')
await page.keyboard.type('# error')
// press arrows to clear autocomplete
await page.keyboard.press('ArrowLeft')
@ -380,10 +294,10 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
// error text on hover
await page.hover('.cm-lint-marker-error')
await expect(page.getByText("found unknown token '$'")).toBeVisible()
await expect(page.getByText("found unknown token '#'")).toBeVisible()
// select the line that's causing the error and delete it
await page.getByText('$ error').click()
await page.getByText('# error').click()
await page.keyboard.press('End')
await page.keyboard.down('Shift')
await page.keyboard.press('Home')
@ -414,7 +328,9 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
})
test('if your kcl gets an error from the engine it is inlined', async ({
/* Ignore this test for now since its causing engine to crash
*
* test('if your kcl gets an error from the engine it is inlined', async ({
page,
}) => {
const u = getUtils(page)
@ -433,7 +349,7 @@ const sketch001 = startSketchOn(box, "revolveAxis")
|> startProfileAt([5, 10], %)
|> line([0, -10], %)
|> line([2, 0], %)
|> line([0, -10], %)
|> line([0, 10], %)
|> close(%)
|> revolve({
axis: getEdge('revolveAxis', box),
@ -448,7 +364,7 @@ angle: 90
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
@ -462,7 +378,7 @@ angle: 90
'sketch profile must lie entirely on one side of the revolution axis'
)
).toBeVisible()
})
})*/
test('executes on load', async ({ page }) => {
const u = getUtils(page)
@ -650,9 +566,7 @@ test('Auto complete works', async ({ page }) => {
await page.keyboard.press('Tab')
await page.keyboard.type('12')
await page.waitForTimeout(100)
await page.keyboard.press('Tab')
await page.waitForTimeout(100)
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')
await page.keyboard.press('Enter')
@ -681,12 +595,13 @@ test('Auto complete works', async ({ page }) => {
test('Stored settings are validated and fall back to defaults', async ({
page,
context,
}) => {
const u = getUtils(page)
// Override beforeEach test setup
// with corrupted settings
await page.addInitScript(
await context.addInitScript(
async ({ settingsKey, settings }) => {
localStorage.setItem(settingsKey, settings)
},
@ -703,18 +618,18 @@ test('Stored settings are validated and fall back to defaults', async ({
// Check the settings were reset
const storedSettings = TOML.parse(
await page.evaluate(
({ settingsKey }) => localStorage.getItem(settingsKey) || '',
({ settingsKey }) => localStorage.getItem(settingsKey) || '{}',
{ settingsKey: TEST_SETTINGS_KEY }
)
) as { settings: SaveSettingsPayload }
expect(storedSettings.settings?.app?.theme).toBe(undefined)
expect(storedSettings.settings.app?.theme).toBe('dark')
// Check that the invalid settings were removed
expect(storedSettings.settings?.modeling?.defaultUnit).toBe(undefined)
expect(storedSettings.settings?.modeling?.mouseControls).toBe(undefined)
expect(storedSettings.settings?.app?.projectDirectory).toBe(undefined)
expect(storedSettings.settings?.projects?.defaultProjectName).toBe(undefined)
expect(storedSettings.settings.modeling?.defaultUnit).toBe(undefined)
expect(storedSettings.settings.modeling?.mouseControls).toBe(undefined)
expect(storedSettings.settings.app?.projectDirectory).toBe(undefined)
expect(storedSettings.settings.projects?.defaultProjectName).toBe(undefined)
})
test('Project settings can be set and override user settings', async ({
@ -765,45 +680,6 @@ test('Project settings can be set and override user settings', async ({
await expect(page.locator('select[name="app-theme"]')).toHaveValue('light')
})
test('Click through each onboarding step', async ({ page }) => {
const u = getUtils(page)
// Override beforeEach test setup
await page.addInitScript(
async ({ settingsKey, settings }) => {
// Give no initial code, so that the onboarding start is shown immediately
localStorage.setItem('persistCode', '')
localStorage.setItem(settingsKey, settings)
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }),
}
)
await page.setViewportSize({ width: 1200, height: 1080 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
// Test that the onboarding pane loaded
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
const nextButton = page.getByTestId('onboarding-next')
while ((await nextButton.innerText()) !== 'Finish') {
await expect(nextButton).toBeVisible()
await nextButton.click()
}
// Finish the onboarding
await expect(nextButton).toBeVisible()
await nextButton.click()
// Test that the onboarding pane is gone
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect(page.url()).not.toContain('onboarding')
})
test('Onboarding redirects and code updating', async ({ page }) => {
const u = getUtils(page)
@ -816,7 +692,7 @@ test('Onboarding redirects and code updating', async ({ page }) => {
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }),
settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING }),
}
)
@ -860,7 +736,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await u.openDebugPanel()
const xAxisClick = () =>
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
page.mouse.click(700, 250).then(() => page.waitForTimeout(100))
const emptySpaceClick = () =>
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
const topHorzSegmentClick = () =>
@ -885,7 +761,6 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
@ -893,14 +768,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
@ -913,14 +786,10 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.getByRole('button', { name: 'Line' }).click()
await u.closeDebugPanel()
const selectionSequence = async (isSecondTime = false) => {
const selectionSequence = async () => {
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(100)
await page.mouse.move(
startXPx + PUR * 15,
isSecondTime ? 430 : 500 - PUR * 10
)
await page.mouse.move(startXPx + PUR * 15, 500 - PUR * 10)
await expect(page.getByTestId('hover-highlight')).toBeVisible()
// bg-yellow-200 is more brittle than hover-highlight, but is closer to the user experience
@ -930,10 +799,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// check mousing off, than mousing onto another line
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(
startXPx + PUR * 10,
isSecondTime ? 295 : 500 - PUR * 20
) // mouse onto another line
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 20) // mouse onto another line
await expect(page.getByTestId('hover-highlight')).toBeVisible()
// now check clicking works including axis
@ -941,31 +807,23 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
await topHorzSegmentClick()
await page.keyboard.down('Shift')
const constrainButton = page.getByRole('button', { name: 'Constrain' })
const absYButton = page.getByRole('button', { name: 'ABS Y' })
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await absYButton.and(page.locator(':not([disabled])')).waitFor()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
await emptySpaceClick()
await page.waitForTimeout(100)
// same selection but click the axis first
await xAxisClick()
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
@ -974,12 +832,9 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// check the same selection again by putting cursor in code first then selecting axis
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
@ -1020,16 +875,11 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.waitForTimeout(100)
// enter sketch again
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
'default_camera_get_settings'
)
await page.waitForTimeout(150)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(300) // wait for animation
// hover again and check it works
await selectionSequence(true)
await selectionSequence()
})
test.describe('Command bar tests', () => {
@ -1041,8 +891,9 @@ test.describe('Command bar tests', () => {
let cmdSearchBar = page.getByPlaceholder('Search commands')
// First try opening the command bar and closing it
// It has a different label on mac and windows/linux, "Meta+K" and "Ctrl+/" respectively
await page
.getByRole('button', { name: 'Commands', exact: false })
.getByRole('button', { name: 'Ctrl+/' })
.or(page.getByRole('button', { name: '⌘K' }))
.click()
await expect(cmdSearchBar).toBeVisible()
@ -1088,9 +939,9 @@ test.describe('Command bar tests', () => {
'persistCode',
`const distance = sqrt(20)
const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 10.98], %)
|> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -20.93], %)
|> line([0.73, -14.93], %)
|> line([-23.44, 0.52], %)
|> close(%)
`
@ -1110,6 +961,7 @@ test.describe('Command bar tests', () => {
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await u.clearCommandLogs()
await page.getByText('|> line([0.73, -14.93], %)').click()
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
let cmdSearchBar = page.getByPlaceholder('Search commands')
@ -1119,12 +971,6 @@ test.describe('Command bar tests', () => {
// Search for extrude command and choose it
await page.getByRole('option', { name: 'Extrude' }).click()
// Assert that we're on the selection step
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
// Select a face
await page.mouse.move(700, 200)
await page.mouse.click(700, 200)
// Assert that we're on the distance step
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
@ -1158,9 +1004,9 @@ test.describe('Command bar tests', () => {
`const distance = sqrt(20)
const distance001 = 5 + 7
const part001 = startSketchOn('-XZ')
|> startProfileAt([-6.95, 10.98], %)
|> startProfileAt([-6.95, 4.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -20.93], %)
|> line([0.73, -14.93], %)
|> line([-23.44, 0.52], %)
|> close(%)
|> extrude(distance001, %)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines
@ -1169,7 +1015,6 @@ const part001 = startSketchOn('-XZ')
})
test('Can add multiple sketches', async ({ page }) => {
test.skip(process.platform === 'darwin', 'Can add multiple sketches')
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -1220,7 +1065,6 @@ test('Can add multiple sketches', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
@ -1236,33 +1080,24 @@ test('Can add multiple sketches', async ({ page }) => {
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.updateCamPosition([100, 100, 100])
await page.waitForTimeout(250)
await u.updateCamPosition([0, 100, 100])
// start a new sketch
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(400)
await page.mouse.click(650, 450)
await page.waitForTimeout(100)
await page.mouse.click(673, 384)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
await u.clearAndCloseDebugPanel()
// on mock os there are issues with getting the camera to update
// it should not be selecting the 'XZ' plane here if the camera updated
// properly, but if we just role with it we can still verify everything
// in the rest of the test
const plane = process.platform === 'darwin' ? 'XZ' : 'XY'
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt2 =
process.platform === 'darwin' ? '[9.75, -13.16]' : '[0.93, -1.25]'
const startAt2 = '[0.93,-1.25]'
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)`.replace(/\s/g, '')
)
await page.waitForTimeout(100)
@ -1271,12 +1106,12 @@ const part002 = startSketchOn('${plane}')
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num2 = process.platform === 'darwin' ? 9.84 : 0.94
const num2 = 0.94
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)`.replace(/\s/g, '')
)
@ -1286,29 +1121,21 @@ const part002 = startSketchOn('${plane}')
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(
num2 + (process.platform === 'darwin' ? 0.01 : -0.01)
)}], %)`.replace(/\s/g, '')
|> line([0, ${roundOff(num2 - 0.01)}], %)`.replace(/\s/g, '')
)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(
num2 + (process.platform === 'darwin' ? 0.01 : -0.01)
)}], %)
|> line([-${process.platform === 'darwin' ? 19.59 : 1.87}, 0], %)`.replace(
/\s/g,
''
)
|> line([0, ${roundOff(num2 - 0.01)}], %)
|> line([-1.87, 0], %)`.replace(/\s/g, '')
)
})
@ -1351,72 +1178,6 @@ test('ProgramMemory can be serialised', async ({ page }) => {
})
})
test('Hovering over 3d features highlights code', async ({ page }) => {
const u = getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
|> startProfileAt([20, 0], %)
|> line([7.13, 4 + 0], %)
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)
|> lineTo([20.14 + 0, -0.14 + 0], %)
|> xLineTo(29 + 0, %)
|> yLine(-3.14 + 0, %, 'a')
|> xLine(1.63, %)
|> angledLineOfXLength({ angle: 3 + 0, length: 3.14 }, %)
|> angledLineOfYLength({ angle: 30, length: 3 + 0 }, %)
|> angledLineToX({ angle: 22.14 + 0, to: 12 }, %)
|> angledLineToY({ angle: 30, to: 11.14 }, %)
|> angledLineThatIntersects({
angle: 3.14,
intersectTag: 'a',
offset: 0
}, %)
|> tangentialArcTo([13.14 + 0, 13.14], %)
|> close(%)
|> extrude(5 + 7, %)
`
)
})
await page.setViewportSize({ width: 1000, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
const extrusionTop: Coords2d = [800, 240]
const flatExtrusionFace: Coords2d = [960, 160]
const arc: Coords2d = [840, 160]
const close: Coords2d = [720, 200]
const nothing: Coords2d = [600, 200]
await page.mouse.move(nothing[0], nothing[1])
await page.mouse.click(nothing[0], nothing[1])
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(200)
await page.mouse.move(extrusionTop[0], extrusionTop[1])
await expect(page.getByTestId('hover-highlight')).toBeVisible()
await page.mouse.move(nothing[0], nothing[1])
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(arc[0], arc[1])
await expect(page.getByTestId('hover-highlight')).toBeVisible()
await page.mouse.move(nothing[0], nothing[1])
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(close[0], close[1])
await expect(page.getByTestId('hover-highlight')).toBeVisible()
await page.mouse.move(nothing[0], nothing[1])
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(flatExtrusionFace[0], flatExtrusionFace[1])
await expect(page.getByTestId('hover-highlight')).toHaveCount(5) // multiple lines
await page.mouse.move(nothing[0], nothing[1])
await page.waitForTimeout(100)
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
})
test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({
page,
}) => {
@ -1551,7 +1312,7 @@ test('Deselecting line tool should mean nothing happens on click', async ({
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(600)
await page.waitForTimeout(300)
let previousCodeContent = await page.locator('.cm-content').innerText()
@ -1578,12 +1339,10 @@ test('Deselecting line tool should mean nothing happens on click', async ({
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(700, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(750, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
@ -1608,16 +1367,16 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
const startPX = [665, 458]
const lineEndPX = [842, 458]
const arcEndPX = [971, 342]
const startPX = [652, 418]
const lineEndPX = [794, 416]
const arcEndPX = [893, 318]
const dragPX = 30
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
await page.waitForTimeout(100)
let prevContent = await page.locator('.cm-content').innerText()
const step5 = { steps: 5 }
@ -1627,7 +1386,7 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
await page.mouse.down()
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
@ -1655,9 +1414,9 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([6.44, -12.07], %)
|> line([14.04, 2.03], %)
|> tangentialArcTo([27.19, -4.2], %)`)
|> startProfileAt([7.01, -11.79], %)
|> line([14.69, 2.73], %)
|> tangentialArcTo([27.6, -3.25], %)`)
})
const doSnapAtDifferentScales = async (
@ -1776,46 +1535,38 @@ test('Sketch on face', async ({ page }) => {
).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(300)
let previousCodeContent = await page.locator('.cm-content').innerText()
await u.openAndClearDebugPanel()
await u.doAndWaitForCmd(
() => page.mouse.click(793, 133),
'default_camera_get_settings',
true
)
await page.waitForTimeout(150)
await page.mouse.click(793, 133)
const firstClickPosition = [612, 238]
const secondClickPosition = [661, 242]
const thirdClickPosition = [609, 267]
await page.waitForTimeout(300)
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([2.87, -0.23], %)
|> line([-3.05, -1.47], %)
|> startProfileAt([1.03, 1.03], %)
|> line([4.18, -0.35], %)
|> line([-4.44, -2.13], %)
|> close(%)`)
await u.openAndClearDebugPanel()
@ -1825,14 +1576,9 @@ test('Sketch on face', async ({ page }) => {
await u.updateCamPosition([1049, 239, 686])
await u.closeDebugPanel()
await page.getByText('startProfileAt([-12.83, 6.7], %)').click()
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
'default_camera_get_settings',
true
)
await page.waitForTimeout(150)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.setViewportSize({ width: 1200, height: 1200 })
await u.openAndClearDebugPanel()
await u.updateCamPosition([452, -152, 1166])
@ -1850,20 +1596,21 @@ test('Sketch on face', async ({ page }) => {
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
const result = makeTemplate`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([${[2.28, 2.35]}, -${0.07}], %)
|> line([-3.05, -1.47], %)
|> close(%)`
await expect(page.locator('.cm-content')).toHaveText(result.regExp)
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([1.03, 1.03], %)
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
}], %)
|> line([-4.44, -2.13], %)
|> close(%)`)
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
await page.getByText('startProfileAt([-12.83, 6.7], %)').click()
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
await page.getByRole('button', { name: 'Extrude' }).click()
@ -1875,9 +1622,15 @@ test('Sketch on face', async ({ page }) => {
await expect(page.getByText('Confirm Extrude')).toBeVisible()
await page.keyboard.press('Enter')
const result2 = result.genNext`
|> extrude(${[5, 5]} + 7, %)`
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([1.03, 1.03], %)
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
}], %)
|> line([-4.44, -2.13], %)
|> close(%)
|> extrude(5 + 7, %)`)
})
test('Can code mod a line length', async ({ page }) => {
@ -1908,12 +1661,11 @@ test('Can code mod a line length', async ({ page }) => {
// enter sketch again
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(350) // wait for animation
await page.waitForTimeout(300) // wait for animation
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.mouse.click(615, 102)
await page.getByRole('button', { name: 'Constrain', exact: true }).click()
await page.mouse.click(615, 133)
await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByText('Add constraining value').click()
@ -1921,42 +1673,3 @@ test('Can code mod a line length', async ({ page }) => {
`const length001 = 20const part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> line([0, 20], %) |> xLine(-length001, %)`
)
})
test('Extrude from command bar selects extrude line after', async ({
page,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> xLine(-20, %)
|> close(%)
`
)
})
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// Click the line of code for xLine.
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Extrude' }).click()
await page.waitForTimeout(100)
await page.keyboard.press('Enter')
await page.waitForTimeout(100)
await page.keyboard.press('Enter')
await page.waitForTimeout(100)
await expect(page.locator('.cm-activeLine')).toHaveText(
` |> extrude(5 + 7, %)`
)
})

View File

@ -507,7 +507,7 @@ test('Draft rectangles should look right', async ({ page, context }) => {
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
await u.closeDebugPanel()
const startXPx = 600
@ -597,15 +597,12 @@ test.describe('Client side scene scale should match engine scale', () => {
// exit sketch
await u.openAndClearDebugPanel()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
200
)
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(300)
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
@ -699,15 +696,12 @@ test.describe('Client side scene scale should match engine scale', () => {
// exit sketch
await u.openAndClearDebugPanel()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Exit Sketch' }).click(),
200
)
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(300)
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,13 +1,12 @@
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { Themes } from 'lib/theme'
export const TEST_SETTINGS_KEY = '/settings.toml'
export const TEST_SETTINGS_KEY = '/user.toml'
export const TEST_SETTINGS = {
app: {
theme: Themes.Dark,
onboardingStatus: 'dismissed',
projectDirectory: '',
enableSSAO: false,
},
modeling: {
defaultUnit: 'in',
@ -22,14 +21,9 @@ export const TEST_SETTINGS = {
},
} satisfies Partial<SaveSettingsPayload>
export const TEST_SETTINGS_ONBOARDING_EXPORT = {
export const TEST_SETTINGS_ONBOARDING = {
...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export' },
} satisfies Partial<SaveSettingsPayload>
export const TEST_SETTINGS_ONBOARDING_START = {
...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboardingStatus: '' },
app: { ...TEST_SETTINGS.app, onboardingStatus: '/export ' },
} satisfies Partial<SaveSettingsPayload>
export const TEST_SETTINGS_CORRUPTED = {

View File

@ -182,76 +182,3 @@ export function getUtils(page: Page) {
}),
}
}
type TemplateOptions = Array<number | Array<number>>
type makeTemplateReturn = {
regExp: RegExp
genNext: (
templateParts: TemplateStringsArray,
...options: TemplateOptions
) => makeTemplateReturn
}
const escapeRegExp = (string: string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}
const _makeTemplate = (
templateParts: TemplateStringsArray,
...options: TemplateOptions
) => {
const length = Math.max(...options.map((a) => (Array.isArray(a) ? a[0] : 0)))
let reExpTemplate = ''
for (let i = 0; i < length; i++) {
const currentStr = templateParts.map((str, index) => {
const currentOptions = options[index]
return (
escapeRegExp(str) +
String(
Array.isArray(currentOptions)
? currentOptions[i]
: typeof currentOptions === 'number'
? currentOptions
: ''
)
)
})
reExpTemplate += '|' + currentStr.join('')
}
return new RegExp(reExpTemplate)
}
/**
* Tool for making templates to match code snippets in the editor with some fudge factor,
* as there's some level of non-determinism.
*
* Usage is as such:
* ```typescript
* const result = makeTemplate`const myVar = aFunc(${[1, 2, 3]})`
* await expect(page.locator('.cm-content')).toHaveText(result.regExp)
* ```
* Where the value `1`, `2` or `3` are all valid and should make the test pass.
*
* The function also has a `genNext` function that allows you to chain multiple templates
* together without having to repeat previous parts of the template.
* ```typescript
* const result2 = result.genNext`const myVar2 = aFunc(${[4, 5, 6]})`
* ```
*/
export const makeTemplate: (
templateParts: TemplateStringsArray,
...values: TemplateOptions
) => makeTemplateReturn = (templateParts, ...options) => {
return {
regExp: _makeTemplate(templateParts, ...options),
genNext: (
nextTemplateParts: TemplateStringsArray,
...nextOptions: TemplateOptions
) =>
makeTemplate(
[...templateParts, ...nextTemplateParts] as any as TemplateStringsArray,
[...options, ...nextOptions] as any
),
}
}

View File

@ -2,7 +2,7 @@ import { browser, $, expect } from '@wdio/globals'
import fs from 'fs/promises'
const documentsDir = `${process.env.HOME}/Documents`
const userSettingsDir = `${process.env.HOME}/.config/dev.zoo.modeling-app`
const userSettingsFile = `${process.env.HOME}/.config/dev.zoo.modeling-app/user.toml`
const defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
const newProjectDir = `${documentsDir}/a-different-directory`
const userCodeDir = '/tmp/kittycad_user_code'
@ -29,10 +29,8 @@ describe('ZMA (Tauri, Linux)', () => {
// Clean up filesystem from previous tests
await new Promise((resolve) => setTimeout(resolve, 100))
await fs.rm(defaultProjectDir, { force: true, recursive: true })
await fs.rm(newProjectDir, { force: true, recursive: true })
await fs.rm(userCodeDir, { force: true })
await fs.rm(userSettingsDir, { force: true, recursive: true })
await fs.mkdir(defaultProjectDir, { recursive: true })
await fs.rm(userSettingsFile, { force: true })
await fs.mkdir(newProjectDir, { recursive: true })
const signInButton = await $('[data-testid="sign-in-button"]')
@ -72,9 +70,8 @@ describe('ZMA (Tauri, Linux)', () => {
console.log(cr.status)
// Now should be signed in
await new Promise((resolve) => setTimeout(resolve, 10000))
const newFileButton = await $('[data-testid="home-new-file"]')
expect(await newFileButton.getText()).toEqual('New project')
expect(await newFileButton.getText()).toEqual('New file')
})
it('opens the settings page, checks filesystem settings, and closes the settings page', async () => {
@ -120,8 +117,8 @@ describe('ZMA (Tauri, Linux)', () => {
it('opens the new file and expects a loading stream', async () => {
const projectLink = await $('[data-testid="project-link"]')
await click(projectLink)
const errorText = await $('[data-testid="unexpected-error"]')
expect(await errorText.getText()).toContain('unexpected error')
const loadingText = await $('[data-testid="loading-stream"]')
expect(await loadingText.getText()).toContain('Loading stream...')
await browser.execute('window.location.href = "tauri://localhost/home"')
})

View File

@ -1,24 +0,0 @@
#!/bin/bash
# Set the repository owner and name
REPO_OWNER="KittyCAD"
REPO_NAME="modeling-app"
WORKFLOW_NAME="build-and-store-wasm.yml"
ARTIFACT_NAME="wasm-bundle"
# Fetch the latest completed workflow run ID for the specified workflow
# RUN_ID=$(gh api repos/$REPO_OWNER/$REPO_NAME/actions/workflows/$WORKFLOW_NAME/runs --paginate --jq '.workflow_runs[] | select(.status=="completed") | .id' | head -n 1)
RUN_ID=$(gh api repos/$REPO_OWNER/$REPO_NAME/actions/workflows/$WORKFLOW_NAME/runs --paginate --jq '.workflow_runs[] | select(.status=="completed" and .conclusion=="success") | .id' | head -n 1)
echo $RUN_ID
# Check if a valid RUN_ID was found
if [ -z "$RUN_ID" ]; then
echo "Failed to find a workflow run for $WORKFLOW_NAME."
exit 1
fi
gh run download $RUN_ID --repo $REPO_OWNER/$REPO_NAME --name $ARTIFACT_NAME --dir ./src/wasm-lib/pkg
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
echo "latest wasm copied to public folder"

View File

@ -15,7 +15,7 @@
<script
defer
data-domain="app.zoo.dev"
src="https://plausible.corp.zoo.dev/js/script.tagged-events.js"
src="https://plausible.corp.zoo.dev/js/script.js"
></script>
<title>Zoo Modeling App</title>
</head>

View File

@ -57,7 +57,7 @@ echo "New version number without 'v': $new_version_number"
git checkout -b "cut-release-$new_version"
echo "$(jq --arg v "$new_version_number" '.version=$v' package.json --indent 2)" > package.json
echo "$(jq --arg v "$new_version_number" '.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json
echo "$(jq --arg v "$new_version_number" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json
git add package.json src-tauri/tauri.conf.json
git commit -m "Cut release $new_version"

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