Compare commits
	
		
			300 Commits
		
	
	
		
			v0.19.0
			...
			wip-multi-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0e02e95c55 | |||
| 3f343698a8 | |||
| 0bd9f42e17 | |||
| aad29fca9f | |||
| a8c1a14d48 | |||
| 8194f8b70b | |||
| 03e4f457d4 | |||
| a94f0191b5 | |||
| 99655d623e | |||
| 4d7cac6c1d | |||
| 59ed422907 | |||
| 3b30ac7d39 | |||
| 226ed37c5f | |||
| 41a6ddd769 | |||
| 0f3f923019 | |||
| bf8fb0d127 | |||
| 4a275c2ff7 | |||
| baf5509f1d | |||
| 47a5e1f6d3 | |||
| d85211c5a4 | |||
| 1beb6b5186 | |||
| 17978ab1d7 | |||
| a1bcad9dfb | |||
| 2e7bdf02cf | |||
| 6f76196b72 | |||
| e7af064518 | |||
| 674d49e2ae | |||
| 4cb48674c6 | |||
| 82daec2aff | |||
| f1ef9d5200 | |||
| dc226d3270 | |||
| 7bf50d8fe0 | |||
| b26764bc9a | |||
| 1b0c6298d7 | |||
| fe9a483726 | |||
| bd42ea037b | |||
| fdb1b21af3 | |||
| 630ef316b8 | |||
| e322926be9 | |||
| a9e61da8b5 | |||
| e2a835a437 | |||
| c61273085f | |||
| a79e365c0f | |||
| 2386ba24e5 | |||
| e42a891df8 | |||
| 98200565bf | |||
| 570fd827ed | |||
| 0add26cf61 | |||
| b54fc534c2 | |||
| c66f851a3f | |||
| 13b8ab71d8 | |||
| bdeab4f87d | |||
| 05ccf5e2f4 | |||
| 7ab015d783 | |||
| 3d6cfa980f | |||
| 9f5f1eb8c3 | |||
| 50fcdff879 | |||
| efaae2b193 | |||
| 7e4ebacb72 | |||
| 72482506c3 | |||
| a51b5b09a3 | |||
| 53ccc1ed6c | |||
| 8106749ccf | |||
| 081e34a600 | |||
| 541400f4be | |||
| 39d249030d | |||
| f8a69fac73 | |||
| 24f4bf160f | |||
| 8011594e24 | |||
| 0e09affb8f | |||
| 197a47346a | |||
| 9d083710e0 | |||
| afa7c1dc4e | |||
| c74b695a71 | |||
| d0c244e05e | |||
| a315b77f02 | |||
| 15c854ff18 | |||
| acd3a5717d | |||
| 8a2555550f | |||
| 62e75c852a | |||
| dd3601ea7b | |||
| a5e7782d9a | |||
| 79b0b70688 | |||
| 1d134c1be0 | |||
| 1c58572234 | |||
| ecee51e82b | |||
| 978ac42f1c | |||
| 893996430e | |||
| 41e65fc4e9 | |||
| 99aa74ceba | |||
| 0bcf33ed00 | |||
| d0a9b5ecab | |||
| a569f818cf | |||
| f73556ba7b | |||
| 29cdc66b34 | |||
| c9800a58d0 | |||
| e46aca4992 | |||
| 9564890b29 | |||
| f8a1f40f20 | |||
| c551d88db4 | |||
| 8eee3e1c58 | |||
| b02529cae0 | |||
| cf03021366 | |||
| f52d2d55f1 | |||
| 59b1319e50 | |||
| b07bbda20b | |||
| 3c01924184 | |||
| bd16902f02 | |||
| 8c3af1a72a | |||
| 33f5d7740d | |||
| b388f60648 | |||
| 8f4380be74 | |||
| 9ae8042a57 | |||
| 4b676d47da | |||
| e6641e68f3 | |||
| 450afb1605 | |||
| 04433fecad | |||
| 6567e2ff92 | |||
| 91c32a7fe2 | |||
| f735cdc22e | |||
| 1b72c7df85 | |||
| 062abd148f | |||
| c93ed0f306 | |||
| 27e2518dde | |||
| dc6505acaf | |||
| 6ff3284eca | |||
| 4cb6ceb043 | |||
| 1db3e1b5e4 | |||
| d797d20d50 | |||
| cf52e151fb | |||
| 87c551b869 | |||
| 2001262494 | |||
| 777b225066 | |||
| ae6373e4f5 | |||
| 87979b17cf | |||
| 4be63e7331 | |||
| 56d930c4f2 | |||
| d48eb0c66c | |||
| a69d7d03d0 | |||
| 00a8273173 | |||
| 51868f892b | |||
| 8e9286a747 | |||
| 023ed1a687 | |||
| 5b7d707b26 | |||
| 5b95194aa7 | |||
| 6080a99e73 | |||
| 5106c49e21 | |||
| 25f18845c7 | |||
| 0a7f1a41fc | |||
| 1625b58577 | |||
| ab6115c4e2 | |||
| fe621240c3 | |||
| 97faf5ae2b | |||
| e3b9a6e5d8 | |||
| e94b1bc12a | |||
| c0eff5bc14 | |||
| b0f92c2f6d | |||
| 718873b3bb | |||
| 9f815eecc1 | |||
| 0384e5e6c6 | |||
| 48ef0885b7 | |||
| 3b2731f924 | |||
| bf4e04f9f1 | |||
| 24475bbcdf | |||
| bcca736a8d | |||
| 440eb2636a | |||
| 344e72d7ec | |||
| ec7b733a0d | |||
| 63159c1cb8 | |||
| df62a995b5 | |||
| fa762c1c4d | |||
| 82b03a9d47 | |||
| 793b7407f6 | |||
| 040bcc2c09 | |||
| ae2e219394 | |||
| a83f549257 | |||
| 3871d2858f | |||
| 3effb87f8e | |||
| 3f2f035a9b | |||
| 4735eaef8c | |||
| 69f8da058a | |||
| 93ebf13621 | |||
| 20c4d44b8b | |||
| 8ea8f80e32 | |||
| d73339fd8d | |||
| 031b230690 | |||
| 1125d74f12 | |||
| 5c7a2822d0 | |||
| d44b1f8e54 | |||
| c4ca69496b | |||
| f06de7f586 | |||
| 75c6ae6e66 | |||
| 48639d70db | |||
| c565d9670d | |||
| 7bf5953299 | |||
| a9ab35e55f | |||
| 15418e98b0 | |||
| 20838bf618 | |||
| acd52ab350 | |||
| 75b9d2913f | |||
| d92e6f6453 | |||
| c1a879837e | |||
| daacca500c | |||
| c1e8bb5288 | |||
| 8ca4166b08 | |||
| 4624f1c0ba | |||
| 7ac6a3a4f2 | |||
| 3cbf2b194a | |||
| 44f06aa199 | |||
| 1b878865b8 | |||
| 3b840e9a80 | |||
| 4a0811eec8 | |||
| e63bf5db11 | |||
| 863e4e206f | |||
| f1cd2355c6 | |||
| 164b675a86 | |||
| b1afe1c541 | |||
| 26ef7218b2 | |||
| e5a4fb439c | |||
| 97ad66a358 | |||
| 26438270ff | |||
| a0cfda6d7a | |||
| 58a62b8097 | |||
| e2909c509f | |||
| 07eaf93e78 | |||
| 6a5ca3088a | |||
| 6501072d80 | |||
| 726fd02bad | |||
| d0f9ae475f | |||
| da323e22d4 | |||
| 8dc3628e9b | |||
| 253744867b | |||
| c45eb1e3e3 | |||
| 758aac9328 | |||
| 309943cf2c | |||
| b3d4ab91fc | |||
| 5e73fa45f0 | |||
| 17d23a17db | |||
| 0460f8eaee | |||
| 2077cdb6fc | |||
| cb0b7e8169 | |||
| 3a05211d30 | |||
| d12d103cba | |||
| 04f6d3dcc8 | |||
| 9c9ffa0d03 | |||
| c62b9f1f04 | |||
| fcac3c72e4 | |||
| 1e2f577a9f | |||
| 1814f340fb | |||
| 43928f88aa | |||
| 6959036688 | |||
| 570d0473c6 | |||
| 44f0d7c25c | |||
| 3ccb04c4e7 | |||
| 00058f699a | |||
| 5a478fe0b3 | |||
| 723cf4f746 | |||
| 3950de0a4d | |||
| 901d474986 | |||
| e7ab645267 | |||
| cf830f9895 | |||
| 2c1f53f0f0 | |||
| d39e2502d0 | |||
| 51fed9c541 | |||
| b3a09abe01 | |||
| cd3a2fea07 | |||
| c29c4a8567 | |||
| 39ccd94884 | |||
| d99ab22b56 | |||
| 20a8f2aa6a | |||
| 93266a9819 | |||
| a9c7a7cb13 | |||
| 8dd9b8d192 | |||
| 23181d8144 | |||
| 834967df6a | |||
| deacaac33a | |||
| c55603853b | |||
| 93f652647e | |||
| 67cea620a6 | |||
| ed0c7d038d | |||
| d3aa789761 | |||
| cd68f80b71 | |||
| d341681c0d | |||
| 0578e9d2a1 | |||
| b413538e9e | |||
| c4e7754fc5 | |||
| 94515b5490 | |||
| aa52407fda | |||
| e45be831d0 | |||
| 005944f3a3 | |||
| 755ef8ce7f | |||
| 005d1f0ca7 | |||
| e158f6f513 | |||
| 879d7ec4f4 | |||
| f6838b9b14 | |||
| cb75c47631 | |||
| 9b95ec1083 | |||
| a3eeff65c8 | |||
| fab3d2b130 | |||
| 0a96dc6fd2 | 
| @ -1,3 +1,3 @@ | ||||
| [codespell] | ||||
| ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast | ||||
| ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue | ||||
| skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,./src-tauri/gen/schemas | ||||
|  | ||||
| @ -3,3 +3,4 @@ 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" | ||||
|  | ||||
							
								
								
									
										4
									
								
								.github/workflows/build-and-store-wasm.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -16,8 +16,6 @@ jobs: | ||||
|           cache: 'yarn' | ||||
|       - name: Install dependencies | ||||
|         run: yarn | ||||
|       - name: Install Playwright Browsers | ||||
|         run: yarn playwright install --with-deps | ||||
|       - name: Setup Rust | ||||
|         uses: dtolnay/rust-toolchain@stable | ||||
|       - name: Cache wasm | ||||
| @ -29,7 +27,7 @@ jobs: | ||||
|  | ||||
|  | ||||
|       # Upload the WASM bundle as an artifact | ||||
|       - uses: actions/upload-artifact@v2 | ||||
|       - uses: actions/upload-artifact@v3 | ||||
|         with: | ||||
|           name: wasm-bundle | ||||
|           path: src/wasm-lib/pkg | ||||
|  | ||||
							
								
								
									
										40
									
								
								.github/workflows/cargo-check.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,40 @@ | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|     paths: | ||||
|       - '**/Cargo.toml' | ||||
|       - '**/Cargo.lock' | ||||
|       - '**/rust-toolchain.toml' | ||||
|       - '**.rs' | ||||
|       - .github/workflows/cargo-check.yml | ||||
|   pull_request: | ||||
| concurrency: | ||||
|   group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | ||||
|   cancel-in-progress: true | ||||
| name: cargo check | ||||
| jobs: | ||||
|   cargocheck: | ||||
|     name: cargo check | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       matrix: | ||||
|         dir: ['src/wasm-lib'] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Install latest rust | ||||
|         uses: actions-rs/toolchain@v1 | ||||
|         with: | ||||
|             toolchain: stable | ||||
|             override: true | ||||
|  | ||||
|       - name: Rust Cache | ||||
|         uses: Swatinem/rust-cache@v2.6.1 | ||||
|  | ||||
|       - name: Run check | ||||
|         run: | | ||||
|           cd "${{ matrix.dir }}" | ||||
|           # We specifically want to test the disable-println feature | ||||
|           # Since it is not enabled by default, we need to specify it | ||||
|           # This is used in kcl-lsp | ||||
|           cargo check --all --features disable-println --features pyo3 | ||||
							
								
								
									
										11
									
								
								.github/workflows/cargo-clippy.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -9,6 +9,12 @@ on: | ||||
|       - '**.rs' | ||||
|       - .github/workflows/cargo-clippy.yml | ||||
|   pull_request: | ||||
|     paths: | ||||
|       - '**/Cargo.toml' | ||||
|       - '**/Cargo.lock' | ||||
|       - '**/rust-toolchain.toml' | ||||
|       - '**.rs' | ||||
|       - .github/workflows/cargo-clippy.yml | ||||
| concurrency: | ||||
|   group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | ||||
|   cancel-in-progress: true | ||||
| @ -54,3 +60,8 @@ jobs: | ||||
|         run: | | ||||
|           cd "${{ matrix.dir }}" | ||||
|           cargo clippy --all --tests --benches -- -D warnings | ||||
|       # If this fails, run "cargo check" to update Cargo.lock, | ||||
|       # then add Cargo.lock to the PR. | ||||
|       - name: Check Cargo.lock doesn't need updating | ||||
|         run: | | ||||
|           cargo check --locked || echo "Pls run cargo check and commit the changed Cargo.lock" | ||||
|  | ||||
							
								
								
									
										122
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -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,7 +130,9 @@ 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 | ||||
| @ -145,6 +147,14 @@ jobs: | ||||
|           cp artifact/src-tauri/tauri.conf.json src-tauri/tauri.conf.json | ||||
|           cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json | ||||
|  | ||||
|       - name: Update WebView2 on Windows | ||||
|         if: matrix.os == 'windows-latest' | ||||
|         # Workaround needed to build the tauri windows app with matching edge version. | ||||
|         # From https://github.com/actions/runner-images/issues/9538 | ||||
|         run: | | ||||
|           Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe' | ||||
|           Start-Process -FilePath setup.exe -Verb RunAs -Wait | ||||
|  | ||||
|       - name: Install ubuntu system dependencies | ||||
|         if: matrix.os == 'ubuntu-latest' | ||||
|         run: | | ||||
| @ -170,9 +180,7 @@ jobs: | ||||
|       - name: Setup Rust | ||||
|         uses: dtolnay/rust-toolchain@stable | ||||
|  | ||||
|       # TODO: re-enable for Windows builds, see https://github.com/tauri-apps/tauri/issues/9045 | ||||
|       - name: Setup Rust cache | ||||
|         if: matrix.os != 'windows-latest' | ||||
|         uses: swatinem/rust-cache@v2 | ||||
|         with: | ||||
|           workspaces: './src-tauri -> target' | ||||
| @ -237,6 +245,96 @@ 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' }} | ||||
| @ -261,11 +359,10 @@ jobs: | ||||
|         with: | ||||
|           path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*" | ||||
|  | ||||
|       # TODO: re-enable linux e2e tests when possible | ||||
|       - name: Run e2e tests (linux only) | ||||
|         if: false | ||||
|         if: ${{ matrix.os == 'ubuntu-latest' && github.event_name != 'release' && github.event_name != 'schedule' }} | ||||
|         run: | | ||||
|           cargo install tauri-driver | ||||
|           cargo install tauri-driver --force | ||||
|           source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }} | ||||
|           export VITE_KC_API_BASE_URL | ||||
|           xvfb-run yarn test:e2e:tauri | ||||
| @ -273,6 +370,17 @@ jobs: | ||||
|           E2E_APPLICATION: "./src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/zoo-modeling-app" | ||||
|           KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }} | ||||
|  | ||||
|       - name: Run e2e tests (windows only) | ||||
|         if: ${{ matrix.os == 'windows-latest' && github.event_name != 'release' && github.event_name != 'schedule' }} | ||||
|         run: | | ||||
|           cargo install tauri-driver --force | ||||
|           yarn wdio run wdio.conf.ts | ||||
|         env: | ||||
|           E2E_APPLICATION: ".\\src-tauri\\target\\${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}\\Zoo Modeling App.exe" | ||||
|           KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }} | ||||
|           VITE_KC_API_BASE_URL: ${{ env.BUILD_RELEASE == 'true' && 'https://api.zoo.dev' || 'https://api.dev.zoo.dev' }} | ||||
|           E2E_TAURI_ENABLED: true | ||||
|           TS_NODE_COMPILER_OPTIONS: '{"module": "commonjs"}' | ||||
|  | ||||
|   publish-apps-release: | ||||
|     runs-on: ubuntu-latest | ||||
| @ -349,7 +457,7 @@ jobs: | ||||
|             cat last_download.json | ||||
|  | ||||
|       - name: Authenticate to Google Cloud | ||||
|         uses: 'google-github-actions/auth@v2.1.2' | ||||
|         uses: 'google-github-actions/auth@v2.1.3' | ||||
|         with: | ||||
|           credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}' | ||||
|  | ||||
|  | ||||
							
								
								
									
										37
									
								
								.github/workflows/create-release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,37 @@ | ||||
| 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) | ||||
							
								
								
									
										106
									
								
								.github/workflows/playwright.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -12,11 +12,31 @@ 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 | ||||
| @ -26,15 +46,46 @@ jobs: | ||||
|     - uses: KittyCAD/action-install-cli@main | ||||
|     - name: Install dependencies | ||||
|       run: yarn | ||||
|     - name: Cache Playwright Browsers | ||||
|       uses: actions/cache@v4 | ||||
|       with: | ||||
|         path: | | ||||
|           ~/.cache/ms-playwright/ | ||||
|         key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }} | ||||
|     - 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@v6 | ||||
|       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 | ||||
|     - name: Cache Wasm (because rust diff) | ||||
|       if: needs.check-rust-changes.outputs.rust-changed == 'true' | ||||
|       uses: Swatinem/rust-cache@v2 | ||||
|       with: | ||||
|         workspaces: './src/wasm-lib' | ||||
|     - name: build wasm | ||||
|     - 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' | ||||
|       run: yarn build:wasm | ||||
|     - name: build web | ||||
|       run: yarn build:local | ||||
| @ -70,7 +121,7 @@ jobs: | ||||
|         git fetch origin | ||||
|         echo ${{ github.head_ref }} | ||||
|         git checkout ${{ github.head_ref }} | ||||
|         # TODO when safari works on ubuntu remove the os part of the commit message | ||||
|         # TODO when webkit works on ubuntu remove the os part of the commit message | ||||
|         git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" || true | ||||
|         git push | ||||
|         git push origin ${{ github.head_ref }} | ||||
| @ -82,13 +133,14 @@ jobs: | ||||
|     - uses: actions/upload-artifact@v3 | ||||
|       if: always() | ||||
|       with: | ||||
|         name: playwright-report | ||||
|         name: playwright-report-ubuntu | ||||
|         path: playwright-report/ | ||||
|         retention-days: 30 | ||||
|  | ||||
|   playwright-macos: | ||||
|     timeout-minutes: 60 | ||||
|     runs-on: macos-14 | ||||
|     needs: check-rust-changes | ||||
|     steps: | ||||
|     - uses: actions/checkout@v4 | ||||
|     - uses: actions/setup-node@v4 | ||||
| @ -97,20 +149,53 @@ jobs: | ||||
|         cache: 'yarn' | ||||
|     - name: Install dependencies | ||||
|       run: yarn | ||||
|     - name: Cache Playwright Browsers | ||||
|       uses: actions/cache@v4 | ||||
|       with: | ||||
|         path: | | ||||
|           ~/.cache/ms-playwright | ||||
|         key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }} | ||||
|         restore-keys: | | ||||
|           ${{ runner.os }}-playwright- | ||||
|     - 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@v6 | ||||
|       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 | ||||
|     - name: Cache Wasm (because rust diff) | ||||
|       if: needs.check-rust-changes.outputs.rust-changed == 'true' | ||||
|       uses: Swatinem/rust-cache@v2 | ||||
|       with: | ||||
|         workspaces: './src/wasm-lib' | ||||
|     - name: build wasm | ||||
|     - 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' | ||||
|       run: yarn build:wasm | ||||
|     - name: build web | ||||
|       run: yarn build:local | ||||
|     - name: Run macos/safari flow | ||||
|       # safari doesn't work on Ubuntu because of the same reason tauri doesn't (webRTC issues) | ||||
|       # webkit doesn't work on Ubuntu because of the same reason tauri doesn't (webRTC issues) | ||||
|       # TODO remove this and the matrix and run all tests on ubuntu when this is fixed | ||||
|       run: yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts | ||||
|       env: | ||||
| @ -119,11 +204,6 @@ jobs: | ||||
|     - uses: actions/upload-artifact@v3 | ||||
|       if: always() | ||||
|       with: | ||||
|         name: playwright-report | ||||
|         name: playwright-report-macos | ||||
|         path: playwright-report/ | ||||
|         retention-days: 30 | ||||
|     - uses: actions/upload-artifact@v2 | ||||
|       if: github.ref == 'refs/heads/main' | ||||
|       with: | ||||
|         name: wasm-bundle | ||||
|         path: src/wasm-lib/pkg | ||||
|  | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -17,6 +17,7 @@ | ||||
| .env.development.local | ||||
| .env.test.local | ||||
| .env.production.local | ||||
| .direnv | ||||
|  | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| @ -54,3 +55,4 @@ src/**/*.typegen.ts | ||||
| src-tauri/gen | ||||
|  | ||||
| src/wasm-lib/grackle/stdlib_cube_partial.json | ||||
| Mac_App_Distribution.provisionprofile | ||||
|  | ||||
							
								
								
									
										14
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,14 @@ | ||||
| .PHONY: dev | ||||
|  | ||||
| WASM_LIB_FILES := $(wildcard src/wasm-lib/**/*.rs) | ||||
|  | ||||
| dev: node_modules public/wasm_lib_bg.wasm | ||||
| 	yarn start | ||||
|  | ||||
| public/wasm_lib_bg.wasm: $(WASM_LIB_FILES) | ||||
| 	yarn build:wasm-dev | ||||
|  | ||||
| node_modules: package.json | ||||
|  | ||||
| package.json: | ||||
| 	yarn install | ||||
							
								
								
									
										80
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @ -60,6 +60,12 @@ 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) | ||||
|  | ||||
| finally, to run the web app only, run: | ||||
| @ -68,7 +74,13 @@ finally, to run the web app only, run: | ||||
| yarn start | ||||
| ``` | ||||
|  | ||||
| ## Developing in Chrome | ||||
| 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 | ||||
|  | ||||
| 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/). | ||||
| @ -90,6 +102,7 @@ yarn test | ||||
| Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default. | ||||
|  | ||||
| For running the rust (not tauri rust though) only, you can | ||||
|  | ||||
| ```bash | ||||
| cd src/wasm-lib | ||||
| cargo test | ||||
| @ -152,6 +165,7 @@ console.log( | ||||
| - `) | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| grab the md list and delete any that are older than the last bump | ||||
|  | ||||
| 2. Merge the PR | ||||
| @ -181,39 +195,49 @@ $ cargo +nightly fuzz run parser | ||||
| For more information on fuzzing you can check out | ||||
| [this guide](https://rust-fuzz.github.io/book/cargo-fuzz.html). | ||||
|  | ||||
|  | ||||
| ### Playwright | ||||
|  | ||||
| First time running plawright locally, you'll need to add the secrets file | ||||
| For a portable way to run Playwright you'll need Docker. | ||||
|  | ||||
| After that, open a terminal and run: | ||||
|  | ||||
| ```bash | ||||
| touch ./e2e/playwright/playwright-secrets.env | ||||
| printf 'token="your-token"\nsnapshottoken="your-snapshot-token"' > ./e2e/playwright/playwright-secrets.env | ||||
| docker run --network host  --rm --init -it playwright/chrome:playwright-1.43.1 | ||||
| ``` | ||||
|  | ||||
| and in another terminal, run: | ||||
|  | ||||
| ```bash | ||||
| 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: | ||||
|  | ||||
|  | ||||
| ```bash | ||||
| # ./e2e/playwright/playwright-secrets.env | ||||
| token=<your-token> | ||||
| snapshottoken=<your-snapshot-token> | ||||
| ``` | ||||
| then replace "your-token" with a dev token from dev.zoo.dev/account/api-tokens | ||||
|  | ||||
| then: | ||||
| run playwright | ||||
| ``` | ||||
| yarn playwright test | ||||
| ``` | ||||
|  | ||||
| run a specific test suite | ||||
| ``` | ||||
| yarn playwright test src/e2e-tests/example.spec.ts | ||||
| ``` | ||||
|  | ||||
| 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) | ||||
|  | ||||
| run headed | ||||
|  | ||||
| ``` | ||||
| yarn playwright test --headed | ||||
| ``` | ||||
|  | ||||
| run with step through debugger | ||||
|  | ||||
| ``` | ||||
| PWDEBUG=1 yarn playwright test | ||||
| ``` | ||||
|  | ||||
| However, if you want a debugger I recommend using VSCode and the `playwright` extension, as the above command is a cruder debugger that steps into every function call which is annoying. | ||||
| With the extension you can set a breakpoint after `waitForDefaultPlanesVisibilityChange` in order to skip app loading, then the vscode debugger's "step over" is much better for being able to stay at the right level of abstraction as you debug the code. | ||||
|  | ||||
| @ -258,7 +282,6 @@ Where `./store` should look like this | ||||
|  | ||||
| </details> | ||||
|  | ||||
|  | ||||
| However because much of our tests involve clicking in the stream at specific locations, it's code-gen looks `await page.locator('video').click();` when really we need to use a pixel coord, so I think it's of limited use. | ||||
|  | ||||
| #### Some notes on CI | ||||
| @ -289,3 +312,26 @@ PS: for the debug panel, the following JSON is useful for snapping the camera | ||||
| ``` | ||||
|  | ||||
| </details> | ||||
|  | ||||
| ### Tauri e2e tests | ||||
|  | ||||
| #### Windows (local only until the CI edge version mismatch is fixed) | ||||
|  | ||||
| ``` | ||||
| yarn install | ||||
| yarn build:wasm-dev | ||||
| cp src/wasm-lib/pkg/wasm_lib_bg.wasm public | ||||
| yarn vite build --mode development | ||||
| yarn tauri build --debug -b | ||||
| $env:KITTYCAD_API_TOKEN="<YOUR_KITTYCAD_API_TOKEN>" | ||||
| $env:VITE_KC_API_BASE_URL="https://api.dev.zoo.dev" | ||||
| $env:E2E_TAURI_ENABLED="true" | ||||
| $env:TS_NODE_COMPILER_OPTIONS='{"module": "commonjs"}' | ||||
| $env:E2E_APPLICATION=".\src-tauri\target\debug\Zoo Modeling App.exe" | ||||
| Stop-Process -Name msedgedriver | ||||
| yarn wdio run wdio.conf.ts | ||||
| ``` | ||||
|  | ||||
| ## KCL | ||||
|  | ||||
| For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl). | ||||
|  | ||||
							
								
								
									
										409
									
								
								docs/kcl/chamfer.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										114
									
								
								docs/kcl/hole.md
									
									
									
									
									
								
							
							
						
						| @ -23,6 +23,7 @@ layout: manual | ||||
| * [`atan`](kcl/atan) | ||||
| * [`bezierCurve`](kcl/bezierCurve) | ||||
| * [`ceil`](kcl/ceil) | ||||
| * [`chamfer`](kcl/chamfer) | ||||
| * [`circle`](kcl/circle) | ||||
| * [`close`](kcl/close) | ||||
| * [`cos`](kcl/cos) | ||||
| @ -31,7 +32,6 @@ layout: manual | ||||
| * [`fillet`](kcl/fillet) | ||||
| * [`floor`](kcl/floor) | ||||
| * [`getEdge`](kcl/getEdge) | ||||
| * [`getExtrudeWallTransform`](kcl/getExtrudeWallTransform) | ||||
| * [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge) | ||||
| * [`getOppositeEdge`](kcl/getOppositeEdge) | ||||
| * [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge) | ||||
| @ -57,11 +57,15 @@ layout: manual | ||||
| * [`patternLinear3d`](kcl/patternLinear3d) | ||||
| * [`pi`](kcl/pi) | ||||
| * [`pow`](kcl/pow) | ||||
| * [`profileStart`](kcl/profileStart) | ||||
| * [`profileStartX`](kcl/profileStartX) | ||||
| * [`profileStartY`](kcl/profileStartY) | ||||
| * [`revolve`](kcl/revolve) | ||||
| * [`segAng`](kcl/segAng) | ||||
| * [`segEndX`](kcl/segEndX) | ||||
| * [`segEndY`](kcl/segEndY) | ||||
| * [`segLen`](kcl/segLen) | ||||
| * [`shell`](kcl/shell) | ||||
| * [`sin`](kcl/sin) | ||||
| * [`sqrt`](kcl/sqrt) | ||||
| * [`startProfileAt`](kcl/startProfileAt) | ||||
|  | ||||
							
								
								
									
										180
									
								
								docs/kcl/profileStart.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										175
									
								
								docs/kcl/profileStartX.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										174
									
								
								docs/kcl/profileStartY.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										397
									
								
								docs/kcl/shell.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										41526
									
								
								docs/kcl/std.json
									
									
									
									
									
								
							
							
						
						| Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 249 KiB | 
| Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 249 KiB | 
| Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 249 KiB | 
| Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 171 KiB | 
| Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 171 KiB | 
| Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 171 KiB | 
| Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 171 KiB | 
| Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 249 KiB | 
| Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 171 KiB | 
| Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 171 KiB | 
| @ -1,10 +1,10 @@ | ||||
| import { test, expect, Download } from '@playwright/test' | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { secrets } from './secrets' | ||||
| import { getUtils } from './test-utils' | ||||
| import { Paths, doExport, getUtils } from './test-utils' | ||||
| import { Models } from '@kittycad/lib' | ||||
| import fsp from 'fs/promises' | ||||
| import { spawn } from 'child_process' | ||||
| import { APP_NAME } from 'lib/constants' | ||||
| import { KCL_DEFAULT_LENGTH } from 'lib/constants' | ||||
| import JSZip from 'jszip' | ||||
| import path from 'path' | ||||
| import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates' | ||||
| @ -20,6 +20,7 @@ test.beforeEach(async ({ page }) => { | ||||
|       localStorage.setItem('TOKEN_PERSIST_KEY', token) | ||||
|       localStorage.setItem('persistCode', ``) | ||||
|       localStorage.setItem(settingsKey, settings) | ||||
|       localStorage.setItem('playwright', 'true') | ||||
|     }, | ||||
|     { | ||||
|       token: secrets.token, | ||||
| @ -44,7 +45,7 @@ test.setTimeout(60_000) | ||||
| test('exports of each format should work', async ({ page, context }) => { | ||||
|   // FYI this test doesn't work with only engine running locally | ||||
|   // And you will need to have the KittyCAD CLI installed | ||||
|   const u = getUtils(page) | ||||
|   const u = await getUtils(page) | ||||
|   await context.addInitScript(async () => { | ||||
|     ;(window as any).playwrightSkipFilePicker = true | ||||
|     localStorage.setItem( | ||||
| @ -98,78 +99,6 @@ const part001 = startSketchOn('-XZ') | ||||
|   await page.waitForTimeout(1000) | ||||
|   await u.clearAndCloseDebugPanel() | ||||
|  | ||||
|   interface Paths { | ||||
|     modelPath: string | ||||
|     imagePath: string | ||||
|     outputType: string | ||||
|   } | ||||
|   const doExport = async ( | ||||
|     output: Models['OutputFormat_type'] | ||||
|   ): Promise<Paths> => { | ||||
|     await page.getByRole('button', { name: APP_NAME }).click() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Export Part' }) | ||||
|     ).toBeVisible() | ||||
|     await page.getByRole('button', { name: 'Export Part' }).click() | ||||
|     await expect(page.getByTestId('command-bar')).toBeVisible() | ||||
|  | ||||
|     // Go through export via command bar | ||||
|     await page.getByRole('option', { name: output.type, exact: false }).click() | ||||
|     await page.locator('#arg-form').waitFor({ state: 'detached' }) | ||||
|     if ('storage' in output) { | ||||
|       await page.getByTestId('arg-name-storage').waitFor({ timeout: 1000 }) | ||||
|       await page.getByRole('button', { name: 'storage', exact: false }).click() | ||||
|       await page | ||||
|         .getByRole('option', { name: output.storage, exact: false }) | ||||
|         .click() | ||||
|       await page.locator('#arg-form').waitFor({ state: 'detached' }) | ||||
|     } | ||||
|     await expect(page.getByText('Confirm Export')).toBeVisible() | ||||
|  | ||||
|     const getPromiseAndResolve = () => { | ||||
|       let resolve: any = () => {} | ||||
|       const promise = new Promise<Download>((r) => { | ||||
|         resolve = r | ||||
|       }) | ||||
|       return [promise, resolve] | ||||
|     } | ||||
|  | ||||
|     const [downloadPromise1, downloadResolve1] = getPromiseAndResolve() | ||||
|     let downloadCnt = 0 | ||||
|  | ||||
|     page.on('download', async (download) => { | ||||
|       if (downloadCnt === 0) { | ||||
|         downloadResolve1(download) | ||||
|       } | ||||
|       downloadCnt++ | ||||
|     }) | ||||
|     await page.getByRole('button', { name: 'Submit command' }).click() | ||||
|  | ||||
|     // Handle download | ||||
|     const download = await downloadPromise1 | ||||
|     const downloadLocationer = (extra = '', isImage = false) => | ||||
|       `./e2e/playwright/export-snapshots/${output.type}-${ | ||||
|         'storage' in output ? output.storage : '' | ||||
|       }${extra}.${isImage ? 'png' : output.type}` | ||||
|     const downloadLocation = downloadLocationer() | ||||
|  | ||||
|     await download.saveAs(downloadLocation) | ||||
|  | ||||
|     if (output.type === 'step') { | ||||
|       // stable timestamps for step files | ||||
|       const fileContents = await fsp.readFile(downloadLocation, 'utf-8') | ||||
|       const newFileContents = fileContents.replace( | ||||
|         /[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+[0-9]+[0-9]\+[0-9]{2}:[0-9]{2}/g, | ||||
|         '1970-01-01T00:00:00.0+00:00' | ||||
|       ) | ||||
|       await fsp.writeFile(downloadLocation, newFileContents) | ||||
|     } | ||||
|     return { | ||||
|       modelPath: downloadLocation, | ||||
|       imagePath: downloadLocationer('', true), | ||||
|       outputType: output.type, | ||||
|     } | ||||
|   } | ||||
|   const axisDirectionPair: Models['AxisDirectionPair_type'] = { | ||||
|     axis: 'z', | ||||
|     direction: 'positive', | ||||
| @ -185,84 +114,114 @@ const part001 = startSketchOn('-XZ') | ||||
|   // just note that only `type` and `storage` are used for selecting the drop downs is the app | ||||
|   // the rest are only there to make typescript happy | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         type: 'step', | ||||
|         coords: sysType, | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         type: 'ply', | ||||
|         coords: sysType, | ||||
|         selection: { type: 'default_scene' }, | ||||
|         storage: 'ascii', | ||||
|         units: 'in', | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         type: 'ply', | ||||
|         storage: 'binary_little_endian', | ||||
|         coords: sysType, | ||||
|         selection: { type: 'default_scene' }, | ||||
|         units: 'in', | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         type: 'ply', | ||||
|         storage: 'binary_big_endian', | ||||
|         coords: sysType, | ||||
|         selection: { type: 'default_scene' }, | ||||
|         units: 'in', | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         type: 'stl', | ||||
|         storage: 'ascii', | ||||
|         coords: sysType, | ||||
|         units: 'in', | ||||
|         selection: { type: 'default_scene' }, | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         type: 'stl', | ||||
|         storage: 'binary', | ||||
|         coords: sysType, | ||||
|         units: 'in', | ||||
|         selection: { type: 'default_scene' }, | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         // obj seems to be a little flaky, times out tests sometimes | ||||
|         type: 'obj', | ||||
|         coords: sysType, | ||||
|         units: 'in', | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         type: 'gltf', | ||||
|         storage: 'embedded', | ||||
|         presentation: 'pretty', | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         type: 'gltf', | ||||
|         storage: 'binary', | ||||
|         presentation: 'pretty', | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|   exportLocations.push( | ||||
|     await doExport({ | ||||
|     await doExport( | ||||
|       { | ||||
|         type: 'gltf', | ||||
|         storage: 'standard', | ||||
|         presentation: 'pretty', | ||||
|     }) | ||||
|       }, | ||||
|       page | ||||
|     ) | ||||
|   ) | ||||
|  | ||||
|   // close page to disconnect websocket since we can only have one open atm | ||||
| @ -273,6 +232,8 @@ const part001 = startSketchOn('-XZ') | ||||
|   for (let { modelPath, imagePath, outputType } of exportLocations) { | ||||
|     // May change depending on the file being dealt with | ||||
|     let cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}` | ||||
|     const fileSize = (await fsp.stat(modelPath)).size | ||||
|     console.log(`Size of the file at ${modelPath}: ${fileSize} bytes`) | ||||
|  | ||||
|     const parentPath = path.dirname(modelPath) | ||||
|  | ||||
| @ -367,7 +328,7 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => { | ||||
|     localStorage.setItem('persistCode', code) | ||||
|   }) | ||||
|  | ||||
|   const u = getUtils(page) | ||||
|   const u = await getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.goto('/') | ||||
|   await u.waitForAuthSkipAppStart() | ||||
| @ -422,7 +383,7 @@ test.describe('extrude on default planes should be stable', () => { | ||||
| }) | ||||
|  | ||||
| test('Draft segments should look right', async ({ page, context }) => { | ||||
|   const u = getUtils(page) | ||||
|   const u = await getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|   await page.goto('/') | ||||
| @ -444,17 +405,16 @@ test('Draft segments should look right', async ({ page, context }) => { | ||||
|   // select a plane | ||||
|   await page.mouse.click(700, 200) | ||||
|  | ||||
|   await expect(page.locator('.cm-content')).toHaveText( | ||||
|     `const part001 = startSketchOn('-XZ')` | ||||
|   ) | ||||
|   let code = `const sketch001 = startSketchOn('XZ')` | ||||
|   await expect(page.locator('.cm-content')).toHaveText(code) | ||||
|  | ||||
|   await page.waitForTimeout(300) // TODO detect animation ending, or disable animation | ||||
|   await page.waitForTimeout(700) // TODO detect animation ending, or disable animation | ||||
|  | ||||
|   const startXPx = 600 | ||||
|   await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt([9.06, -12.22], %)`) | ||||
|   code += ` | ||||
|   |> startProfileAt([7.19, -9.7], %)` | ||||
|   await expect(page.locator('.cm-content')).toHaveText(code) | ||||
|   await page.waitForTimeout(100) | ||||
|  | ||||
|   await u.closeDebugPanel() | ||||
| @ -466,10 +426,9 @@ test('Draft segments should look right', async ({ page, context }) => { | ||||
|   await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|   await page.waitForTimeout(100) | ||||
|  | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt([9.06, -12.22], %) | ||||
|   |> line([9.14, 0], %)`) | ||||
|   code += ` | ||||
|   |> line([7.25, 0], %)` | ||||
|   await expect(page.locator('.cm-content')).toHaveText(code) | ||||
|  | ||||
|   await page.getByRole('button', { name: 'Tangential Arc' }).click() | ||||
|  | ||||
| @ -481,7 +440,7 @@ test('Draft segments should look right', async ({ page, context }) => { | ||||
| }) | ||||
|  | ||||
| test('Draft rectangles should look right', async ({ page, context }) => { | ||||
|   const u = getUtils(page) | ||||
|   const u = await getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|   await page.goto('/') | ||||
| @ -504,10 +463,10 @@ test('Draft rectangles should look right', async ({ page, context }) => { | ||||
|   await page.mouse.click(700, 200) | ||||
|  | ||||
|   await expect(page.locator('.cm-content')).toHaveText( | ||||
|     `const part001 = startSketchOn('-XZ')` | ||||
|     `const sketch001 = startSketchOn('XZ')` | ||||
|   ) | ||||
|  | ||||
|   await page.waitForTimeout(300) // TODO detect animation ending, or disable animation | ||||
|   await page.waitForTimeout(500) // TODO detect animation ending, or disable animation | ||||
|   await u.closeDebugPanel() | ||||
|  | ||||
|   const startXPx = 600 | ||||
| @ -528,7 +487,7 @@ test('Draft rectangles should look right', async ({ page, context }) => { | ||||
|  | ||||
| test.describe('Client side scene scale should match engine scale', () => { | ||||
|   test('Inch scale', async ({ page }) => { | ||||
|     const u = getUtils(page) | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.goto('/') | ||||
| @ -552,17 +511,16 @@ test.describe('Client side scene scale should match engine scale', () => { | ||||
|     // select a plane | ||||
|     await page.mouse.click(700, 200) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `const part001 = startSketchOn('-XZ')` | ||||
|     ) | ||||
|     let code = `const sketch001 = startSketchOn('XZ')` | ||||
|     await expect(page.locator('.cm-content')).toHaveText(code) | ||||
|  | ||||
|     await page.waitForTimeout(300) // TODO detect animation ending, or disable animation | ||||
|     await page.waitForTimeout(600) // TODO detect animation ending, or disable animation | ||||
|  | ||||
|     const startXPx = 600 | ||||
|     await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|     |> startProfileAt([9.06, -12.22], %)`) | ||||
|     code += ` | ||||
|   |> startProfileAt([7.19, -9.7], %)` | ||||
|     await expect(u.codeLocator).toHaveText(code) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await u.closeDebugPanel() | ||||
| @ -570,21 +528,18 @@ test.describe('Client side scene scale should match engine scale', () => { | ||||
|     await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|     |> startProfileAt([9.06, -12.22], %) | ||||
|     |> line([9.14, 0], %)`) | ||||
|     code += ` | ||||
|   |> line([7.25, 0], %)` | ||||
|     await expect(u.codeLocator).toHaveText(code) | ||||
|  | ||||
|     await page.getByRole('button', { name: 'Tangential Arc' }).click() | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|     |> startProfileAt([9.06, -12.22], %) | ||||
|     |> line([9.14, 0], %) | ||||
|     |> tangentialArcTo([27.34, -3.08], %)`) | ||||
|     code += ` | ||||
|   |> tangentialArcTo([21.7, -2.44], %)` | ||||
|     await expect(u.codeLocator).toHaveText(code) | ||||
|  | ||||
|     // click tangential arc tool again to unequip it | ||||
|     await page.getByRole('button', { name: 'Tangential Arc' }).click() | ||||
| @ -597,12 +552,15 @@ test.describe('Client side scene scale should match engine scale', () => { | ||||
|  | ||||
|     // exit sketch | ||||
|     await u.openAndClearDebugPanel() | ||||
|     await page.getByRole('button', { name: 'Exit Sketch' }).click() | ||||
|     await u.doAndWaitForImageDiff( | ||||
|       () => page.getByRole('button', { name: 'Exit Sketch' }).click(), | ||||
|       200 | ||||
|     ) | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.clearAndCloseDebugPanel() | ||||
|     await page.waitForTimeout(200) | ||||
|     await page.waitForTimeout(300) | ||||
|  | ||||
|     // second screen shot should look almost identical, i.e. scale should be the same. | ||||
|     await expect(page).toHaveScreenshot({ | ||||
| @ -628,7 +586,7 @@ test.describe('Client side scene scale should match engine scale', () => { | ||||
|         }), | ||||
|       } | ||||
|     ) | ||||
|     const u = getUtils(page) | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.goto('/') | ||||
| @ -652,17 +610,16 @@ test.describe('Client side scene scale should match engine scale', () => { | ||||
|     // select a plane | ||||
|     await page.mouse.click(700, 200) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `const part001 = startSketchOn('-XZ')` | ||||
|     ) | ||||
|     let code = `const sketch001 = startSketchOn('XZ')` | ||||
|     await expect(u.codeLocator).toHaveText(code) | ||||
|  | ||||
|     await page.waitForTimeout(300) // TODO detect animation ending, or disable animation | ||||
|     await page.waitForTimeout(600) // TODO detect animation ending, or disable animation | ||||
|  | ||||
|     const startXPx = 600 | ||||
|     await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|       |> startProfileAt([230.03, -310.32], %)`) | ||||
|     code += ` | ||||
|   |> startProfileAt([182.59, -246.32], %)` | ||||
|     await expect(u.codeLocator).toHaveText(code) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await u.closeDebugPanel() | ||||
| @ -670,21 +627,18 @@ test.describe('Client side scene scale should match engine scale', () => { | ||||
|     await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|       |> startProfileAt([230.03, -310.32], %) | ||||
|       |> line([232.2, 0], %)`) | ||||
|     code += ` | ||||
|   |> line([184.3, 0], %)` | ||||
|     await expect(u.codeLocator).toHaveText(code) | ||||
|  | ||||
|     await page.getByRole('button', { name: 'Tangential Arc' }).click() | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|       |> startProfileAt([230.03, -310.32], %) | ||||
|       |> line([232.2, 0], %) | ||||
|       |> tangentialArcTo([694.43, -78.12], %)`) | ||||
|     code += ` | ||||
|   |> tangentialArcTo([551.2, -62.01], %)` | ||||
|     await expect(u.codeLocator).toHaveText(code) | ||||
|  | ||||
|     await page.getByRole('button', { name: 'Tangential Arc' }).click() | ||||
|     await page.waitForTimeout(100) | ||||
| @ -696,12 +650,15 @@ test.describe('Client side scene scale should match engine scale', () => { | ||||
|  | ||||
|     // exit sketch | ||||
|     await u.openAndClearDebugPanel() | ||||
|     await page.getByRole('button', { name: 'Exit Sketch' }).click() | ||||
|     await u.doAndWaitForImageDiff( | ||||
|       () => page.getByRole('button', { name: 'Exit Sketch' }).click(), | ||||
|       200 | ||||
|     ) | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.clearAndCloseDebugPanel() | ||||
|     await page.waitForTimeout(200) | ||||
|     await page.waitForTimeout(300) | ||||
|  | ||||
|     // second screen shot should look almost identical, i.e. scale should be the same. | ||||
|     await expect(page).toHaveScreenshot({ | ||||
| @ -711,8 +668,8 @@ test.describe('Client side scene scale should match engine scale', () => { | ||||
| }) | ||||
|  | ||||
| test('Sketch on face with none z-up', async ({ page, context }) => { | ||||
|   const u = getUtils(page) | ||||
|   await context.addInitScript(async () => { | ||||
|   const u = await getUtils(page) | ||||
|   await context.addInitScript(async (KCL_DEFAULT_LENGTH) => { | ||||
|     localStorage.setItem( | ||||
|       'persistCode', | ||||
|       `const part001 = startSketchOn('-XZ') | ||||
| @ -720,16 +677,16 @@ test('Sketch on face with none z-up', async ({ page, context }) => { | ||||
|   |> line([9.31, 10.55], %, 'seg01') | ||||
|   |> line([11.91, -10.42], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5 + 7, %) | ||||
|   |> extrude(${KCL_DEFAULT_LENGTH}, %) | ||||
| const part002 = startSketchOn(part001, 'seg01') | ||||
|   |> startProfileAt([8, 8], %) | ||||
|   |> line([4.68, 3.05], %) | ||||
|   |> line([0, -7.79], %, 'seg02') | ||||
|   |> close(%) | ||||
|   |> extrude(5 + 7, %) | ||||
|   |> extrude(${KCL_DEFAULT_LENGTH}, %) | ||||
| ` | ||||
|     ) | ||||
|   }) | ||||
|   }, KCL_DEFAULT_LENGTH) | ||||
|  | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.goto('/') | ||||
| @ -765,3 +722,76 @@ const part002 = startSketchOn(part001, 'seg01') | ||||
|     maxDiffPixels: 100, | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test('Zoom to fit on load - solid 2d', async ({ page, context }) => { | ||||
|   const u = await getUtils(page) | ||||
|   await context.addInitScript(async () => { | ||||
|     localStorage.setItem( | ||||
|       'persistCode', | ||||
|       `const part001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%) | ||||
| ` | ||||
|     ) | ||||
|   }, KCL_DEFAULT_LENGTH) | ||||
|  | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.goto('/') | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|   await u.openDebugPanel() | ||||
|   // wait for execution done | ||||
|   await expect( | ||||
|     page.locator('[data-message-type="execution-done"]') | ||||
|   ).toHaveCount(2) | ||||
|   await u.closeDebugPanel() | ||||
|  | ||||
|   // Wait for the second extrusion to appear | ||||
|   // TODO: Find a way to truly know that the objects have finished | ||||
|   // rendering, because an execution-done message is not sufficient. | ||||
|   await page.waitForTimeout(1000) | ||||
|  | ||||
|   await expect(page).toHaveScreenshot({ | ||||
|     maxDiffPixels: 100, | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test('Zoom to fit on load - solid 3d', async ({ page, context }) => { | ||||
|   const u = await getUtils(page) | ||||
|   await context.addInitScript(async () => { | ||||
|     localStorage.setItem( | ||||
|       'persistCode', | ||||
|       `const part001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%) | ||||
|   |> extrude(10, %) | ||||
| ` | ||||
|     ) | ||||
|   }, KCL_DEFAULT_LENGTH) | ||||
|  | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.goto('/') | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|   await u.openDebugPanel() | ||||
|   // wait for execution done | ||||
|   await expect( | ||||
|     page.locator('[data-message-type="execution-done"]') | ||||
|   ).toHaveCount(2) | ||||
|   await u.closeDebugPanel() | ||||
|  | ||||
|   // Wait for the second extrusion to appear | ||||
|   // TODO: Find a way to truly know that the objects have finished | ||||
|   // rendering, because an execution-done message is not sufficient. | ||||
|   await page.waitForTimeout(1000) | ||||
|  | ||||
|   await expect(page).toHaveScreenshot({ | ||||
|     maxDiffPixels: 100, | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 42 KiB | 
| Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 42 KiB | 
| Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 49 KiB | 
